diff --git a/animation-server/CMakeLists.txt b/animation-server/CMakeLists.txt index 7d3222327a..5d9f3604bc 100644 --- a/animation-server/CMakeLists.txt +++ b/animation-server/CMakeLists.txt @@ -29,4 +29,18 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +# link the hifi networking library +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") +find_package(GnuTLS REQUIRED) + +# include the GnuTLS dir +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +# link GnuTLS +target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/animation-server/src/AnimationServer.cpp b/animation-server/src/AnimationServer.cpp index 6a4d1ea76d..ffb98b4ae0 100644 --- a/animation-server/src/AnimationServer.cpp +++ b/animation-server/src/AnimationServer.cpp @@ -726,12 +726,12 @@ AnimationServer::AnimationServer(int &argc, char **argv) : ::wantLocalDomain = cmdOptionExists(argc, (const char**) argv,local); if (::wantLocalDomain) { printf("Local Domain MODE!\n"); - nodeList->getDomainInfo().setIPToLocalhost(); + nodeList->getDomainHandler().setIPToLocalhost(); } const char* domainHostname = getCmdOption(argc, (const char**) argv, "--domain"); if (domainHostname) { - NodeList::getInstance()->getDomainInfo().setHostname(domainHostname); + NodeList::getInstance()->getDomainHandler().setHostname(domainHostname); } const char* packetsPerSecondCommand = getCmdOption(argc, (const char**) argv, "--pps"); @@ -800,11 +800,11 @@ AnimationServer::AnimationServer(int &argc, char **argv) : QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readPendingDatagrams())); } diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 505708a899..a8343e94ad 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -30,9 +30,19 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}") +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + if (UNIX) target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS}) endif (UNIX) @@ -41,4 +51,4 @@ IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f376c4e981..f48c4b9401 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -149,7 +149,7 @@ void Agent::run() { // figure out the URL for the script for this agent assignment QString scriptURLString("http://%1:8080/assignment/%2"); - scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainInfo().getIP().toString(), + scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), uuidStringWithoutCurlyBraces(_uuid)); QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 9d9bfa3113..f0d23cac62 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include #include #include @@ -32,64 +34,58 @@ int hifiSockAddrMeta = qRegisterMetaType("HifiSockAddr"); AssignmentClient::AssignmentClient(int &argc, char **argv) : QCoreApplication(argc, argv), - _currentAssignment() + _currentAssignment(), + _assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME) { + DTLSClientSession::globalInit(); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("assignment-client"); QSettings::setDefaultFormat(QSettings::IniFormat); + QStringList argumentList = arguments(); + // register meta type is required for queued invoke method on Assignment subclasses // set the logging target to the the CHILD_TARGET_NAME Logging::setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); - const char ASSIGNMENT_TYPE_OVVERIDE_OPTION[] = "-t"; - const char* assignmentTypeString = getCmdOption(argc, (const char**)argv, ASSIGNMENT_TYPE_OVVERIDE_OPTION); + const QString ASSIGNMENT_TYPE_OVVERIDE_OPTION = "-t"; + int argumentIndex = argumentList.indexOf(ASSIGNMENT_TYPE_OVVERIDE_OPTION); Assignment::Type requestAssignmentType = Assignment::AllTypes; - if (assignmentTypeString) { - // the user is asking to only be assigned to a particular type of assignment - // so set that as the ::overridenAssignmentType to be used in requests - requestAssignmentType = (Assignment::Type) atoi(assignmentTypeString); + if (argumentIndex != -1) { + requestAssignmentType = (Assignment::Type) argumentList[argumentIndex + 1].toInt(); } - const char ASSIGNMENT_POOL_OPTION[] = "--pool"; - const char* requestAssignmentPool = getCmdOption(argc, (const char**) argv, ASSIGNMENT_POOL_OPTION); + const QString ASSIGNMENT_POOL_OPTION = "--pool"; + argumentIndex = argumentList.indexOf(ASSIGNMENT_POOL_OPTION); + QString assignmentPool; + + if (argumentIndex != -1) { + assignmentPool = argumentList[argumentIndex + 1]; + } // setup our _requestAssignment member variable from the passed arguments - _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, requestAssignmentPool); + _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); // create a NodeList as an unassigned client NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned); - const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a"; - const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p"; + // check for an overriden assignment server hostname + const QString CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION = "-a"; - // grab the overriden assignment-server hostname from argv, if it exists - const char* customAssignmentServerHostname = getCmdOption(argc, (const char**)argv, CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION); - const char* customAssignmentServerPortString = getCmdOption(argc,(const char**)argv, CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION); + argumentIndex = argumentList.indexOf(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION); - HifiSockAddr customAssignmentSocket; - - if (customAssignmentServerHostname || customAssignmentServerPortString) { + if (argumentIndex != -1) { + _assignmentServerHostname = argumentList[argumentIndex + 1]; - // set the custom port or default if it wasn't passed - unsigned short assignmentServerPort = customAssignmentServerPortString - ? atoi(customAssignmentServerPortString) : DEFAULT_DOMAIN_SERVER_PORT; + // set the custom assignment socket on our NodeList + HifiSockAddr customAssignmentSocket = HifiSockAddr(_assignmentServerHostname, DEFAULT_DOMAIN_SERVER_PORT); - // set the custom hostname or default if it wasn't passed - if (!customAssignmentServerHostname) { - customAssignmentServerHostname = DEFAULT_ASSIGNMENT_SERVER_HOSTNAME; - } - - customAssignmentSocket = HifiSockAddr(customAssignmentServerHostname, assignmentServerPort); - } - - // set the custom assignment socket if we have it - if (!customAssignmentSocket.isNull()) { nodeList->setAssignmentServerSocket(customAssignmentSocket); } @@ -108,6 +104,10 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : this, &AssignmentClient::handleAuthenticationRequest); } +AssignmentClient::~AssignmentClient() { + DTLSClientSession::globalDeinit(); +} + void AssignmentClient::sendAssignmentRequest() { if (!_currentAssignment) { NodeList::getInstance()->sendAssignment(_requestAssignment); @@ -133,12 +133,12 @@ void AssignmentClient::readPendingDatagrams() { if (_currentAssignment) { qDebug() << "Received an assignment -" << *_currentAssignment; - // switch our nodelist domain IP and port to whoever sent us the assignment + // switch our DomainHandler hostname and port to whoever sent us the assignment - nodeList->getDomainInfo().setSockAddr(senderSockAddr); - nodeList->getDomainInfo().setAssignmentUUID(_currentAssignment->getUUID()); + nodeList->getDomainHandler().setSockAddr(senderSockAddr, _assignmentServerHostname); + nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID()); - qDebug() << "Destination IP for assignment is" << nodeList->getDomainInfo().getIP().toString(); + qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString(); // start the deployed assignment AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this); diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 1ad8cca244..89fe74f044 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -20,6 +20,7 @@ class AssignmentClient : public QCoreApplication { Q_OBJECT public: AssignmentClient(int &argc, char **argv); + ~AssignmentClient(); private slots: void sendAssignmentRequest(); void readPendingDatagrams(); @@ -28,6 +29,7 @@ private slots: private: Assignment _requestAssignment; SharedAssignmentPointer _currentAssignment; + QString _assignmentServerHostname; }; #endif // hifi_AssignmentClient_h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 35b522ff2f..407a64d7a8 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -21,7 +21,6 @@ #include #ifdef _WIN32 -#include "Syssocket.h" #include "Systime.h" #include #else @@ -372,7 +371,7 @@ void AudioMixer::sendStatsPacket() { statsObject["average_mixes_per_listener"] = 0.0; } - ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); +// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; _sumMixes = 0; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 9289d640f5..f35285124f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "AvatarMixerClientData.h" AvatarMixerClientData::AvatarMixerClientData() : diff --git a/cmake/modules/FindGnuTLS.cmake b/cmake/modules/FindGnuTLS.cmake new file mode 100644 index 0000000000..0e1899864b --- /dev/null +++ b/cmake/modules/FindGnuTLS.cmake @@ -0,0 +1,41 @@ +# +# FindGnuTLS.cmake +# +# Try to find the GnuTLS library +# +# You can provide a GNUTLS_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# GNUTLS_FOUND - system found GnuTLS +# GNUTLS_INCLUDE_DIR - the GnuTLS include directory +# GNUTLS_LIBRARY - Link this to use GnuTLS +# +# Created on 3/31/2014 by Stephen Birarda +# Copyright (c) 2014 High Fidelity +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +if (GNUTLS_LIBRARY AND GNUTLS_INCLUDE_DIRS) + # in cache already + set(GNUTLS_FOUND TRUE) +else () + set(GNUTLS_SEARCH_DIRS "${GNUTLS_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/gnutls") + + find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h PATH_SUFFIXES include HINTS ${GNUTLS_SEARCH_DIRS}) + + find_library(GNUTLS_LIBRARY NAMES gnutls libgnutls libgnutls-28 PATH_SUFFIXES lib HINTS ${GNUTLS_SEARCH_DIRS}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GNUTLS DEFAULT_MSG GNUTLS_INCLUDE_DIR GNUTLS_LIBRARY) + + if (WIN32 AND NOT GNUTLS_FOUND) + message(STATUS "If you're generating a MSVC environment, you'll need to run the command") + message(STATUS "$GnuTLS-DIR\\bin>lib /def:libgnutls-28.def") + message(STATUS "From the MSVC command prompt to generate the .lib file and copy it into") + message(STATUS "the GnuTLS lib folder. Replace $GnuTLS-DIR in the command with the directory") + message(STATUS "containing GnuTLS.") + endif () +endif () diff --git a/cmake/modules/FindQxmpp.cmake b/cmake/modules/FindQxmpp.cmake index d5f23143f6..420186d969 100644 --- a/cmake/modules/FindQxmpp.cmake +++ b/cmake/modules/FindQxmpp.cmake @@ -9,7 +9,7 @@ # # QXMPP_FOUND - system found qxmpp # QXMPP_INCLUDE_DIRS - the qxmpp include directory -# QXMPP_LIBRARIES - Link this to use qxmpp +# QXMPP_LIBRARY - Link this to use qxmpp # # Created on 3/10/2014 by Stephen Birarda # Copyright 2014 High Fidelity, Inc. @@ -31,14 +31,4 @@ else () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(QXMPP DEFAULT_MSG QXMPP_INCLUDE_DIR QXMPP_LIBRARY) - - if (QXMPP_FOUND) - if (NOT QXMPP_FIND_QUIETLY) - message(STATUS "Found qxmpp: ${QXMPP_LIBRARY}") - endif () - else () - if (QXMPP_FIND_REQUIRED) - message(FATAL_ERROR "Could not find qxmpp") - endif () - endif () endif () \ No newline at end of file diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 16bdb0df6b..4736c2438b 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -17,6 +17,7 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") find_package(Qt5Network REQUIRED) +find_package(GnuTLS REQUIRED) include(${MACRO_DIR}/SetupHifiProject.cmake) @@ -33,11 +34,21 @@ add_custom_command(TARGET ${TARGET_NAME} POST_BUILD # link the shared hifi library include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}") +# include the GnuTLS dir +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Network) \ No newline at end of file +# link QtNetwork and GnuTLS +target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/domain-server/src/DTLSServerSession.cpp b/domain-server/src/DTLSServerSession.cpp new file mode 100644 index 0000000000..a80c54ee02 --- /dev/null +++ b/domain-server/src/DTLSServerSession.cpp @@ -0,0 +1,18 @@ +// +// DTLSServerSession.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DTLSServerSession.h" + +DTLSServerSession::DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) : + DTLSSession(GNUTLS_SERVER, dtlsSocket, destinationSocket) +{ + +} \ No newline at end of file diff --git a/domain-server/src/DTLSServerSession.h b/domain-server/src/DTLSServerSession.h new file mode 100644 index 0000000000..5fdc602df7 --- /dev/null +++ b/domain-server/src/DTLSServerSession.h @@ -0,0 +1,24 @@ +// +// DTLSServerSession.h +// domain-server/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DTLSServerSession_h +#define hifi_DTLSServerSession_h + +#include + +#include + +class DTLSServerSession : public DTLSSession { +public: + DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); +}; + +#endif // hifi_DTLSServerSession_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 05d1fcd534..1bda2aa75c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - #include #include #include @@ -19,13 +17,17 @@ #include #include +#include + #include +#include #include #include #include #include #include "DomainServerNodeData.h" +#include "DummyDTLSSession.h" #include "DomainServer.h" @@ -36,126 +38,180 @@ DomainServer::DomainServer(int argc, char* argv[]) : _HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _staticAssignmentHash(), _assignmentQueue(), - _nodeAuthenticationURL(), - _redeemedTokenResponses() + _isUsingDTLS(false), + _x509Credentials(NULL), + _dhParams(NULL), + _priorityCache(NULL), + _dtlsSessions() { + gnutls_global_init(); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); QSettings::setDefaultFormat(QSettings::IniFormat); - _argumentList = arguments(); - int argumentIndex = 0; + _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); - // check if this domain server should use no authentication or a custom hostname for authentication - const QString DEFAULT_AUTH_OPTION = "--defaultAuth"; - const QString CUSTOM_AUTH_OPTION = "--customAuth"; - if ((argumentIndex = _argumentList.indexOf(DEFAULT_AUTH_OPTION) != -1)) { - _nodeAuthenticationURL = QUrl(DEFAULT_NODE_AUTH_URL); - } else if ((argumentIndex = _argumentList.indexOf(CUSTOM_AUTH_OPTION)) != -1) { - _nodeAuthenticationURL = QUrl(_argumentList.value(argumentIndex + 1)); + if (optionallySetupDTLS()) { + // we either read a certificate and private key or were not passed one, good to load assignments + // and set up the node list + qDebug() << "Setting up LimitedNodeList and assignments."; + setupNodeListAndAssignments(); + + if (_isUsingDTLS) { + // we're using DTLS and our NodeList socket is good to go, so make the required DTLS changes + // DTLS requires that IP_DONTFRAG be set + // This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it + + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + +#if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER) + qDebug() << "Making required DTLS changes to NodeList DTLS socket."; + + int socketHandle = LimitedNodeList::getInstance()->getDTLSSocket().socketDescriptor(); +#if defined(IP_DONTFRAG) + int optValue = 1;yea + setsockopt(socketHandle, IPPROTO_IP, IP_DONTFRAG, (const void*) optValue, sizeof(optValue)); +#elif defined(IP_MTU_DISCOVER) + int optValue = 1; + setsockopt(socketHandle, IPPROTO_IP, IP_MTU_DISCOVER, (const void*) optValue, sizeof(optValue)); +#endif +#endif + // connect our socket to read datagrams received on the DTLS socket + connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams); + } } +} + +DomainServer::~DomainServer() { + if (_x509Credentials) { + gnutls_certificate_free_credentials(*_x509Credentials); + gnutls_priority_deinit(*_priorityCache); + gnutls_dh_params_deinit(*_dhParams); + + delete _x509Credentials; + delete _priorityCache; + delete _dhParams; + delete _cookieKey; + } + gnutls_global_deinit(); +} + +bool DomainServer::optionallySetupDTLS() { + if (readX509KeyAndCertificate()) { + if (_x509Credentials) { + qDebug() << "Generating Diffie-Hellman parameters."; + + // generate Diffie-Hellman parameters + // When short bit length is used, it might be wise to regenerate parameters often. + int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY); + + _dhParams = new gnutls_dh_params_t; + gnutls_dh_params_init(_dhParams); + gnutls_dh_params_generate2(*_dhParams, dhBits); + + qDebug() << "Successfully generated Diffie-Hellman parameters."; + + // set the D-H paramters on the X509 credentials + gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams); + + // setup the key used for cookie verification + _cookieKey = new gnutls_datum_t; + gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE); + + _priorityCache = new gnutls_priority_t; + const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE"; + gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL); + + _isUsingDTLS = true; + + qDebug() << "Initial DTLS setup complete."; + } - if (!_nodeAuthenticationURL.isEmpty()) { - const QString DATA_SERVER_USERNAME_ENV = "HIFI_DS_USERNAME"; - const QString DATA_SERVER_PASSWORD_ENV = "HIFI_DS_PASSWORD"; + return true; + } else { + return false; + } +} + +bool DomainServer::readX509KeyAndCertificate() { + const QString X509_CERTIFICATE_OPTION = "cert"; + const QString X509_PRIVATE_KEY_OPTION = "key"; + const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; + + QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString(); + QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString(); + + if (!certPath.isEmpty() && !keyPath.isEmpty()) { + // the user wants to use DTLS to encrypt communication with nodes + // let's make sure we can load the key and certificate + _x509Credentials = new gnutls_certificate_credentials_t; + gnutls_certificate_allocate_credentials(_x509Credentials); - // this node will be using an authentication server, let's make sure we have a username/password - QProcessEnvironment sysEnvironment = QProcessEnvironment::systemEnvironment(); + QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); - QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV); - QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV); + qDebug() << "Reading certificate file at" << certPath << "for DTLS."; + qDebug() << "Reading key file at" << keyPath << "for DTLS."; - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_nodeAuthenticationURL); + int gnutlsReturn = gnutls_certificate_set_x509_key_file2(*_x509Credentials, + certPath.toLocal8Bit().constData(), + keyPath.toLocal8Bit().constData(), + GNUTLS_X509_FMT_PEM, + keyPassphraseString.toLocal8Bit().constData(), + 0); - if (!username.isEmpty() && !password.isEmpty()) { - - connect(&accountManager, &AccountManager::loginComplete, this, &DomainServer::requestCreationFromDataServer); - - // ask the account manager to log us in from the env variables - accountManager.requestAccessToken(username, password); - } else { - qDebug() << "Authentication was requested against" << qPrintable(_nodeAuthenticationURL.toString()) - << "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV) - << "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Qutting!"; - - // bail out + if (gnutlsReturn < 0) { + qDebug() << "Unable to load certificate or key file." << "Error" << gnutlsReturn << "- domain-server will now quit."; QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); - - return; + return false; } - } else { - // auth is not requested for domain-server, setup NodeList and assignments now - setupNodeListAndAssignments(); + qDebug() << "Successfully read certificate and private key."; + + } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { + qDebug() << "Missing certificate or private key. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + return false; } -} - -void DomainServer::requestCreationFromDataServer() { - // this slot is fired when we get a valid access token from the data-server - // now let's ask it to set us up with a UUID - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "processCreateResponseFromDataServer"; - AccountManager::getInstance().authenticatedRequest("/api/v1/domains/create", - QNetworkAccessManager::PostOperation, - callbackParams); -} - -void DomainServer::processCreateResponseFromDataServer(const QJsonObject& jsonObject) { - if (jsonObject["status"].toString() == "success") { - // pull out the UUID the data-server is telling us to use, and complete our setup with it - QUuid newSessionUUID = QUuid(jsonObject["data"].toObject()["uuid"].toString()); - setupNodeListAndAssignments(newSessionUUID); - } -} - -void DomainServer::processTokenRedeemResponse(const QJsonObject& jsonObject) { - // pull out the registration token this is associated with - QString registrationToken = jsonObject["data"].toObject()["registration_token"].toString(); - - // if we have a registration token add it to our hash of redeemed token responses - if (!registrationToken.isEmpty()) { - qDebug() << "Redeemed registration token" << registrationToken; - _redeemedTokenResponses.insert(registrationToken, jsonObject); - } + return true; } void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - int argumentIndex = 0; - - const QString CUSTOM_PORT_OPTION = "-p"; + const QString CUSTOM_PORT_OPTION = "port"; unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT; - if ((argumentIndex = _argumentList.indexOf(CUSTOM_PORT_OPTION)) != -1) { - domainServerPort = _argumentList.value(argumentIndex + 1).toUShort(); + if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) { + domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt(); + } + + unsigned short domainServerDTLSPort = 0; + + if (_isUsingDTLS) { + domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; + + const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; + + if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) { + domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); + } } QSet parsedTypes(QSet() << Assignment::AgentType); - parseCommandLineTypeConfigs(_argumentList, parsedTypes); - - const QString CONFIG_FILE_OPTION = "--configFile"; - if ((argumentIndex = _argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) { - QString configFilePath = _argumentList.value(argumentIndex + 1); - readConfigFile(configFilePath, parsedTypes); - } + parseAssignmentConfigs(parsedTypes); populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); - NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort); + LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); - // create a random UUID for this session for the domain-server - nodeList->setSessionUUID(sessionUUID); - - connect(nodeList, &NodeList::nodeAdded, this, &DomainServer::nodeAdded); - connect(nodeList, &NodeList::nodeKilled, this, &DomainServer::nodeKilled); + connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); + connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams())); @@ -163,130 +219,78 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes) { +void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { // check for configs from the command line, these take precedence - const QString CONFIG_TYPE_OPTION = "--configType"; - int clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION); + const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; + QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); - // enumerate all CL config overrides and parse them to files - while (clConfigIndex != -1) { - int clConfigType = argumentList.value(clConfigIndex + 1).toInt(); - if (clConfigType < Assignment::AllTypes && !excludedTypes.contains((Assignment::Type) clConfigIndex)) { - Assignment::Type assignmentType = (Assignment::Type) clConfigType; - createStaticAssignmentsForTypeGivenConfigString((Assignment::Type) assignmentType, - argumentList.value(clConfigIndex + 2)); - excludedTypes.insert(assignmentType); - } + // scan for assignment config keys + QStringList variantMapKeys = _argumentVariantMap.keys(); + int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); + + while (configIndex != -1) { + // figure out which assignment type this matches + Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt(); - clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION, clConfigIndex + 1); - } -} - -// Attempts to read configuration from specified path -// returns true on success, false otherwise -void DomainServer::readConfigFile(const QString& path, QSet& excludedTypes) { - if (path.isEmpty()) { - // config file not specified - return; - } - - if (!QFile::exists(path)) { - qWarning("Specified configuration file does not exist!"); - return; - } - - QFile configFile(path); - if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning("Can't open specified configuration file!"); - return; - } else { - qDebug() << "Reading configuration from" << path; - } - - QTextStream configStream(&configFile); - QByteArray configStringByteArray = configStream.readAll().toUtf8(); - QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); - configFile.close(); - - QSet appendedExcludedTypes = excludedTypes; - - foreach (const QString& rootStringValue, configDocObject.keys()) { - int possibleConfigType = rootStringValue.toInt(); - - if (possibleConfigType < Assignment::AllTypes - && !excludedTypes.contains((Assignment::Type) possibleConfigType)) { - // this is an appropriate config type and isn't already in our excluded types - // we are good to parse it - Assignment::Type assignmentType = (Assignment::Type) possibleConfigType; - QString configString = readServerAssignmentConfig(configDocObject, rootStringValue); - createStaticAssignmentsForTypeGivenConfigString(assignmentType, configString); + if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { + QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]]; + + if (mapValue.type() == QVariant::String) { + QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8()); + createStaticAssignmentsForType(assignmentType, deserializedDocument.array()); + } else { + createStaticAssignmentsForType(assignmentType, mapValue.toJsonValue().toArray()); + } excludedTypes.insert(assignmentType); } + + configIndex = variantMapKeys.indexOf(assignmentConfigRegex, configIndex + 1); } } -// find assignment configurations on the specified node name and json object -// returns a string in the form of its equivalent cmd line params -QString DomainServer::readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName) { - QJsonArray nodeArray = jsonObject[nodeName].toArray(); - - QStringList serverConfig; - foreach (const QJsonValue& childValue, nodeArray) { - QString cmdParams; - QJsonObject childObject = childValue.toObject(); - QStringList keys = childObject.keys(); - for (int i = 0; i < keys.size(); i++) { - QString key = keys[i]; - QString value = childObject[key].toString(); - // both cmd line params and json keys are the same - cmdParams += QString("--%1 %2 ").arg(key, value); - } - serverConfig << cmdParams; - } - - // according to split() calls from DomainServer::prepopulateStaticAssignmentFile - // we shold simply join them with semicolons - return serverConfig.join(';'); -} - void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) { qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash."; _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); } -void DomainServer::createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString) { +void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) { // we have a string for config for this type - qDebug() << "Parsing command line config for assignment type" << type; + qDebug() << "Parsing config for assignment type" << type; - QStringList multiConfigList = configString.split(";", QString::SkipEmptyParts); + int configCounter = 0; - const QString ASSIGNMENT_CONFIG_POOL_REGEX = "--pool\\s*([\\w-]+)"; - QRegExp poolRegex(ASSIGNMENT_CONFIG_POOL_REGEX); - - // read each config to a payload for this type of assignment - for (int i = 0; i < multiConfigList.size(); i++) { - QString config = multiConfigList.at(i); - - // check the config string for a pool - QString assignmentPool; - - int poolIndex = poolRegex.indexIn(config); - - if (poolIndex != -1) { - assignmentPool = poolRegex.cap(1); + foreach(const QJsonValue& jsonValue, configArray) { + if (jsonValue.isObject()) { + QJsonObject jsonObject = jsonValue.toObject(); - // remove the pool from the config string, the assigned node doesn't need it - config.remove(poolIndex, poolRegex.matchedLength()); + // check the config string for a pool + const QString ASSIGNMENT_POOL_KEY = "pool"; + QString assignmentPool; + + QJsonValue poolValue = jsonObject[ASSIGNMENT_POOL_KEY]; + if (!poolValue.isUndefined()) { + assignmentPool = poolValue.toString(); + + jsonObject.remove(ASSIGNMENT_POOL_KEY); + } + + ++configCounter; + qDebug() << "Type" << type << "config" << configCounter << "=" << jsonObject; + + Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); + + // setup the payload as a semi-colon separated list of key = value + QStringList payloadStringList; + foreach(const QString& payloadKey, jsonObject.keys()) { + QString dashes = payloadKey.size() == 1 ? "-" : "--"; + payloadStringList << QString("%1%2 %3").arg(dashes).arg(payloadKey).arg(jsonObject[payloadKey].toString()); + } + + configAssignment->setPayload(payloadStringList.join(' ').toUtf8()); + + addStaticAssignmentToAssignmentHash(configAssignment); } - - qDebug("Type %d config[%d] = %s", type, i, config.toLocal8Bit().constData()); - - Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); - - configAssignment->setPayload(config.toUtf8()); - - addStaticAssignmentToAssignmentHash(configAssignment); } } @@ -302,21 +306,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSetgetNodeSocket().writeDatagram(authenticationRequestPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); -} - const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer; @@ -347,10 +336,14 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe // create a new session UUID for this node QUuid nodeUUID = QUuid::createUuid(); - SharedNodePointer newNode = NodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); + SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); - // when the newNode is created the linked data is also created, if this was a static assignment set the UUID - reinterpret_cast(newNode->getLinkedData())->setStaticAssignmentUUID(assignmentUUID); + // when the newNode is created the linked data is also created + // if this was a static assignment set the UUID, set the sendingSockAddr + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + + nodeData->setStaticAssignmentUUID(assignmentUUID); + nodeData->setSendingSockAddr(senderSockAddr); if (!authJsonObject.isEmpty()) { // pull the connection secret from the authJsonObject and set it as the connection secret for this node @@ -430,10 +423,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - NodeList* nodeList = NodeList::getInstance(); - + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); if (nodeInterestList.size() > 0) { + + DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; + unsigned int dataMTU = dtlsSession ? gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; + // if the node has any interest types, send back those nodes as well foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { @@ -463,11 +459,15 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif nodeDataStream << secretUUID; - if (broadcastPacket.size() + nodeByteArray.size() > MAX_PACKET_SIZE) { + if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { // we need to break here and start a new packet // so send the current one - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } // reset the broadcastPacket structure broadcastPacket.resize(numBroadcastPacketLeadBytes); @@ -478,78 +478,180 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif broadcastPacket.append(nodeByteArray); } } + + // always write the last broadcastPacket + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } } - - // always write the last broadcastPacket - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); } void DomainServer::readAvailableDatagrams() { - NodeList* nodeList = NodeList::getInstance(); - AccountManager& accountManager = AccountManager::getInstance(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); HifiSockAddr senderSockAddr; + QByteArray receivedPacket; static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment); static int numAssignmentPacketHeaderBytes = assignmentPacket.size(); - - QByteArray receivedPacket; while (nodeList->getNodeSocket().hasPendingDatagrams()) { receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(), senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - - if (nodeList->packetVersionAndHashMatch(receivedPacket)) { - PacketType requestType = packetTypeForPacket(receivedPacket); + if (packetTypeForPacket(receivedPacket) == PacketTypeRequestAssignment + && nodeList->packetVersionAndHashMatch(receivedPacket)) { + + // construct the requested assignment from the packet data + Assignment requestAssignment(receivedPacket); - if (requestType == PacketTypeDomainConnectRequest) { - QDataStream packetStream(receivedPacket); - packetStream.skipRawData(numBytesForPacketHeader(receivedPacket)); + // Suppress these for Assignment::AgentType to once per 5 seconds + static quint64 lastNoisyMessage = usecTimestampNow(); + quint64 timeNow = usecTimestampNow(); + const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND; + bool noisyMessage = false; + if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + qDebug() << "Received a request for assignment type" << requestAssignment.getType() + << "from" << senderSockAddr; + noisyMessage = true; + } + + SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); + + if (assignmentToDeploy) { + qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr; - quint8 hasRegistrationToken; - packetStream >> hasRegistrationToken; + // give this assignment out, either the type matches or the requestor said they will take any + assignmentPacket.resize(numAssignmentPacketHeaderBytes); - if (requiresAuthentication() && !hasRegistrationToken) { - // we need authentication and this node did not give us a registration token - tell it to auth - requestAuthenticationFromPotentialNode(senderSockAddr); - } else if (requiresAuthentication()) { - QByteArray registrationToken; - packetStream >> registrationToken; - - QString registrationTokenString(registrationToken.toHex()); - QJsonObject jsonForRedeemedToken = _redeemedTokenResponses.value(registrationTokenString); - - // check if we have redeemed this token and are ready to check the node in - if (jsonForRedeemedToken.isEmpty()) { - // make a request against the data-server to get information required to connect to this node - JSONCallbackParameters tokenCallbackParams; - tokenCallbackParams.jsonCallbackReceiver = this; - tokenCallbackParams.jsonCallbackMethod = "processTokenRedeemResponse"; - - QString redeemURLString = QString("/api/v1/nodes/redeem/%1.json").arg(registrationTokenString); - accountManager.authenticatedRequest(redeemURLString, QNetworkAccessManager::GetOperation, - tokenCallbackParams); - } else if (jsonForRedeemedToken["status"].toString() != "success") { - // we redeemed the token, but it was invalid - get the node to get another - requestAuthenticationFromPotentialNode(senderSockAddr); - } else { - // we've redeemed the token for this node and are ready to start communicating with it - // add the node to our NodeList - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr, jsonForRedeemedToken); - } - - // if it exists, remove this response from the in-memory hash - _redeemedTokenResponses.remove(registrationTokenString); - - } else { - // we don't require authentication - add this node to our NodeList - // and send back session UUID right away - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); + + assignmentStream << *assignmentToDeploy.data(); + + nodeList->getNodeSocket().writeDatagram(assignmentPacket, + senderSockAddr.getAddress(), senderSockAddr.getPort()); + } else { + if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() + << "from" << senderSockAddr; + noisyMessage = true; } + } + + if (noisyMessage) { + lastNoisyMessage = timeNow; + } + } else if (!_isUsingDTLS) { + // not using DTLS, process datagram normally + processDatagram(receivedPacket, senderSockAddr); + } else { + // we're using DTLS, so tell the sender to get back to us using DTLS + static QByteArray dtlsRequiredPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerRequireDTLS); + static int numBytesDTLSHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerRequireDTLS); + + if (dtlsRequiredPacket.size() == numBytesDTLSHeader) { + // pack the port that we accept DTLS traffic on + unsigned short dtlsPort = nodeList->getDTLSSocket().localPort(); + dtlsRequiredPacket.replace(numBytesDTLSHeader, sizeof(dtlsPort), reinterpret_cast(&dtlsPort)); + } + + nodeList->writeUnverifiedDatagram(dtlsRequiredPacket, senderSockAddr); + } + } +} + +void DomainServer::readAvailableDTLSDatagrams() { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + QUdpSocket& dtlsSocket = nodeList->getDTLSSocket(); + + static sockaddr senderSockAddr; + static socklen_t sockAddrSize = sizeof(senderSockAddr); + + while (dtlsSocket.hasPendingDatagrams()) { + // check if we have an active DTLS session for this sender + QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0); + + recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(), + MSG_PEEK, &senderSockAddr, &sockAddrSize); + + HifiSockAddr senderHifiSockAddr(&senderSockAddr); + DTLSServerSession* existingSession = _dtlsSessions.value(senderHifiSockAddr); + + if (existingSession) { + if (!existingSession->completedHandshake()) { + // check if we have completed handshake with this user + int handshakeReturn = gnutls_handshake(*existingSession->getGnuTLSSession()); - } else if (requestType == PacketTypeDomainListRequest) { - QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + if (handshakeReturn == 0) { + existingSession->setCompletedHandshake(true); + } else if (gnutls_error_is_fatal(handshakeReturn)) { + // this was a fatal error handshaking, so remove this session + qDebug() << "Fatal error -" << gnutls_strerror(handshakeReturn) << "- during DTLS handshake with" + << senderHifiSockAddr; + _dtlsSessions.remove(senderHifiSockAddr); + } + } else { + // pull the data from this user off the stack and process it + int receivedBytes = gnutls_record_recv(*existingSession->getGnuTLSSession(), + peekDatagram.data(), peekDatagram.size()); + if (receivedBytes > 0) { + processDatagram(peekDatagram.left(receivedBytes), senderHifiSockAddr); + } else if (gnutls_error_is_fatal(receivedBytes)) { + qDebug() << "Fatal error -" << gnutls_strerror(receivedBytes) << "- during DTLS handshake with" + << senderHifiSockAddr; + } + } + } else { + // first we verify the cookie + // see http://gnutls.org/manual/html_node/DTLS-sessions.html for why this is required + gnutls_dtls_prestate_st prestate; + memset(&prestate, 0, sizeof(prestate)); + int cookieValid = gnutls_dtls_cookie_verify(_cookieKey, &senderSockAddr, sizeof(senderSockAddr), + peekDatagram.data(), peekDatagram.size(), &prestate); + + if (cookieValid < 0) { + // the cookie sent by the client was not valid + // send a valid one + DummyDTLSSession tempServerSession(LimitedNodeList::getInstance()->getDTLSSocket(), senderHifiSockAddr); + + gnutls_dtls_cookie_send(_cookieKey, &senderSockAddr, sizeof(senderSockAddr), &prestate, + &tempServerSession, DTLSSession::socketPush); + + // acutally pull the peeked data off the network stack so that it gets discarded + dtlsSocket.readDatagram(peekDatagram.data(), peekDatagram.size()); + } else { + // cookie valid but no existing session - set up a new session now + DTLSServerSession* newServerSession = new DTLSServerSession(LimitedNodeList::getInstance()->getDTLSSocket(), + senderHifiSockAddr); + gnutls_session_t* gnutlsSession = newServerSession->getGnuTLSSession(); + + gnutls_priority_set(*gnutlsSession, *_priorityCache); + gnutls_credentials_set(*gnutlsSession, GNUTLS_CRD_CERTIFICATE, *_x509Credentials); + gnutls_dtls_prestate_set(*gnutlsSession, &prestate); + + // handshake to begin the session + gnutls_handshake(*gnutlsSession); + + qDebug() << "Beginning DTLS session with node at" << senderHifiSockAddr; + _dtlsSessions[senderHifiSockAddr] = newServerSession; + } + } + } +} + +void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + PacketType requestType = packetTypeForPacket(receivedPacket); + + if (requestType == PacketTypeDomainListRequest) { + QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + + if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) { NodeType_t throwawayNodeType; HifiSockAddr nodePublicAddress, nodeLocalAddress; @@ -557,60 +659,22 @@ void DomainServer::readAvailableDatagrams() { receivedPacket, senderSockAddr); SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress); - + // update last receive to now quint64 timeNow = usecTimestampNow(); checkInNode->setLastHeardMicrostamp(timeNow); - - + sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes)); - - } else if (requestType == PacketTypeRequestAssignment) { - - // construct the requested assignment from the packet data - Assignment requestAssignment(receivedPacket); - - // Suppress these for Assignment::AgentType to once per 5 seconds - static quint64 lastNoisyMessage = usecTimestampNow(); - quint64 timeNow = usecTimestampNow(); - const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND; - bool noisyMessage = false; - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { - qDebug() << "Received a request for assignment type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; - } - - SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); - - if (assignmentToDeploy) { - qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr; - - // give this assignment out, either the type matches or the requestor said they will take any - assignmentPacket.resize(numAssignmentPacketHeaderBytes); - - QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); - - assignmentStream << *assignmentToDeploy.data(); - - nodeList->getNodeSocket().writeDatagram(assignmentPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); - } else { - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { - qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; - } - } - - if (noisyMessage) { - lastNoisyMessage = timeNow; - } - } else if (requestType == PacketTypeNodeJsonStats) { - SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); - if (matchingNode) { - reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); - } + } else { + // new node - add this node to our NodeList + // and send back session UUID right away + addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + } + + } else if (requestType == PacketTypeNodeJsonStats) { + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (matchingNode) { + reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); } } } @@ -679,7 +743,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject assignedNodesJSON; // enumerate the NodeList to find the assigned nodes - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { if (_staticAssignmentHash.value(node->getUUID())) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); @@ -721,7 +785,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonArray nodesJSONArray; // enumerate the NodeList to find the assigned nodes - NodeList* nodeList = NodeList::getInstance(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // add the node using the UUID as the key @@ -745,7 +809,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUuid matchingUUID = QUuid(nodeShowRegex.cap(1)); // see if we have a node that matches this ID - SharedNodePointer matchingNode = NodeList::getInstance()->nodeWithUUID(matchingUUID); + SharedNodePointer matchingNode = LimitedNodeList::getInstance()->nodeWithUUID(matchingUUID); if (matchingNode) { // create a QJsonDocument with the stats QJsonObject QJsonObject statsObject = @@ -824,14 +888,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // pull the captured string, if it exists QUuid deleteUUID = QUuid(nodeDeleteRegex.cap(1)); - SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); + SharedNodePointer nodeToKill = LimitedNodeList::getInstance()->nodeWithUUID(deleteUUID); if (nodeToKill) { // start with a 200 response connection->respond(HTTPConnection::StatusCode200); // we have a valid UUID and node - kill the node that has this assignment - QMetaObject::invokeMethod(NodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); + QMetaObject::invokeMethod(LimitedNodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); // successfully processed request return true; @@ -840,7 +904,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (allNodesDeleteRegex.indexIn(url.path()) != -1) { qDebug() << "Received request to kill all nodes."; - NodeList::getInstance()->eraseAllNodes(); + LimitedNodeList::getInstance()->eraseAllNodes(); return true; } @@ -875,6 +939,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) { void DomainServer::nodeKilled(SharedNodePointer node) { DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + if (nodeData) { // if this node's UUID matches a static assignment we need to throw it back in the assignment queue if (!nodeData->getStaticAssignmentUUID().isNull()) { @@ -887,11 +952,19 @@ void DomainServer::nodeKilled(SharedNodePointer node) { // cleanup the connection secrets that we set up for this node (on the other nodes) foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) { - SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); + SharedNodePointer otherNode = LimitedNodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); if (otherNode) { reinterpret_cast(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID()); } } + + if (_isUsingDTLS) { + // check if we need to remove a DTLS session from our in-memory hash + DTLSServerSession* existingSession = _dtlsSessions.take(nodeData->getSendingSockAddr()); + if (existingSession) { + delete existingSession; + } + } } } @@ -972,7 +1045,7 @@ void DomainServer::addStaticAssignmentsToQueue() { bool foundMatchingAssignment = false; // enumerate the nodes and check if there is one with an attached assignment with matching UUID - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { if (node->getUUID() == staticAssignment->data()->getUUID()) { foundMatchingAssignment = true; } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c2f0eee995..1400fadee3 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -20,18 +20,21 @@ #include #include +#include + #include #include #include +#include "DTLSServerSession.h" + typedef QSharedPointer SharedAssignmentPointer; class DomainServer : public QCoreApplication, public HTTPRequestHandler { Q_OBJECT public: DomainServer(int argc, char* argv[]); - - bool requiresAuthentication() const { return !_nodeAuthenticationURL.isEmpty(); } + ~DomainServer(); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); @@ -43,10 +46,17 @@ public slots: /// Called by NodeList to inform us a node has been killed void nodeKilled(SharedNodePointer node); +private slots: + + void readAvailableDatagrams(); + void readAvailableDTLSDatagrams(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + bool optionallySetupDTLS(); + bool readX509KeyAndCertificate(); + + void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); - void requestAuthenticationFromPotentialNode(const HifiSockAddr& senderSockAddr); void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr, const QJsonObject& authJsonObject = QJsonObject()); int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, @@ -55,11 +65,9 @@ private: void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, const NodeSet& nodeInterestList); - void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes); - void readConfigFile(const QString& path, QSet& excludedTypes); - QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName); + void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); - void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString); + void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray); void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes); SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType); @@ -76,17 +84,15 @@ private: QHash _staticAssignmentHash; QQueue _assignmentQueue; - QUrl _nodeAuthenticationURL; + QVariantMap _argumentVariantMap; - QStringList _argumentList; + bool _isUsingDTLS; + gnutls_certificate_credentials_t* _x509Credentials; + gnutls_dh_params_t* _dhParams; + gnutls_datum_t* _cookieKey; + gnutls_priority_t* _priorityCache; - QHash _redeemedTokenResponses; -private slots: - void requestCreationFromDataServer(); - void processCreateResponseFromDataServer(const QJsonObject& jsonObject); - void processTokenRedeemResponse(const QJsonObject& jsonObject); - - void readAvailableDatagrams(); + QHash _dtlsSessions; }; #endif // hifi_DomainServer_h diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index 9e118faff6..59d60659de 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -20,7 +20,8 @@ DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), _staticAssignmentUUID(), - _statsJSONObject() + _statsJSONObject(), + _sendingSockAddr() { } diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 3b73ec17d2..6026e65f25 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -15,6 +15,7 @@ #include #include +#include #include class DomainServerNodeData : public NodeData { @@ -29,6 +30,9 @@ public: void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; } const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; } + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } + const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } + QHash& getSessionSecretHash() { return _sessionSecretHash; } private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); @@ -36,6 +40,7 @@ private: QHash _sessionSecretHash; QUuid _staticAssignmentUUID; QJsonObject _statsJSONObject; + HifiSockAddr _sendingSockAddr; }; #endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 2c8c3bd4c4..bdb10cb56f 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -26,7 +26,7 @@ int main(int argc, char* argv[]) { #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - + qInstallMessageHandler(Logging::verboseMessageHandler); DomainServer domainServer(argc, argv); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4e58d898af..6295095b43 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -33,8 +33,8 @@ elseif (UNIX) # include the right GL headers for UNIX set(GL_HEADERS "#include \n#include \n#include ") elseif (WIN32) - add_definitions( -D_USE_MATH_DEFINES ) # apparently needed to get M_PI and other defines from cmath/math.h - add_definitions( -DWINDOWS_LEAN_AND_MEAN ) # needed to make sure windows doesn't go to crazy with its defines + add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h + add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines set(GL_HEADERS "#include \n#include \n#include ") endif () @@ -68,6 +68,7 @@ foreach(EXTERNAL_SOURCE_SUBDIR ${EXTERNAL_SOURCE_SUBDIRS}) endforeach(EXTERNAL_SOURCE_SUBDIR) find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) +find_package(GnuTLS REQUIRED) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) @@ -120,6 +121,7 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}") @@ -190,9 +192,14 @@ endif (QXMPP_FOUND AND NOT DISABLE_QXMPP) # include headers for interface and InterfaceConfig. include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes") +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + # include external library headers # use system flag so warnings are supressed -include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}") +include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") target_link_libraries( ${TARGET_NAME} @@ -200,6 +207,7 @@ target_link_libraries( "${ZLIB_LIBRARIES}" Qt5::Core Qt5::Gui Qt5::Multimedia Qt5::Network Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKit Qt5::WebKitWidgets Qt5::Xml Qt5::UiTools + "${GNUTLS_LIBRARY}" ) if (APPLE) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d63fa2293e..1fabca3711 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,6 +170,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _previousScriptLocation(), _logger(new FileLogger(this)) { + // init GnuTLS for DTLS with domain-servers + DTLSClientSession::globalInit(); + // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -227,8 +230,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : audioThread->start(); - connect(&nodeList->getDomainInfo(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - connect(&nodeList->getDomainInfo(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); @@ -275,7 +278,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QTimer* silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); @@ -398,6 +401,8 @@ Application::~Application() { delete _glWidget; AccountManager::getInstance().destroy(); + + DTLSClientSession::globalDeinit(); } void Application::saveSettings() { @@ -3046,8 +3051,7 @@ void Application::updateWindowTitle(){ QString username = AccountManager::getInstance().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() - + " @ " + nodeList->getDomainInfo().getHostname() + buildVersion; - + + " @ " + nodeList->getDomainHandler().getHostname() + buildVersion; qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } diff --git a/interface/src/Audio.h b/interface/src/Audio.h index bfb3450d72..b78bcc661e 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -13,7 +13,6 @@ #define hifi_Audio_h #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8ad55dec5b..98ab86c2e1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -782,23 +782,22 @@ void Menu::editPreferences() { } void Menu::goToDomain(const QString newDomain) { - if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) { - + if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect Application::getInstance()->getAvatar()->sendKillAvatar(); // give our nodeList the new domain-server hostname - NodeList::getInstance()->getDomainInfo().setHostname(newDomain); + NodeList::getInstance()->getDomainHandler().setHostname(newDomain); } } void Menu::goToDomainDialog() { - QString currentDomainHostname = NodeList::getInstance()->getDomainInfo().getHostname(); + QString currentDomainHostname = NodeList::getInstance()->getDomainHandler().getHostname(); - if (NodeList::getInstance()->getDomainInfo().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { + if (NodeList::getInstance()->getDomainHandler().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { // add the port to the currentDomainHostname string if it is custom - currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainInfo().getPort())); + currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainHandler().getPort())); } QInputDialog domainDialog(Application::getInstance()->getWindow()); @@ -991,7 +990,7 @@ void Menu::nameLocation() { connect(manager, &LocationManager::creationCompleted, this, &Menu::namedLocationCreated); NamedLocation* location = new NamedLocation(locationName, myAvatar->getPosition(), myAvatar->getOrientation(), - NodeList::getInstance()->getDomainInfo().getHostname()); + NodeList::getInstance()->getDomainHandler().getHostname()); manager->createNamedLocation(location); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 64b0bb49f9..8229611646 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1163,7 +1163,7 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { QStringList coordinateItems = positionString.split(','); QStringList orientationItems = orientationString.split(','); - NodeList::getInstance()->getDomainInfo().setHostname(domainHostnameString); + NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString); // orient the user to face the target glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), diff --git a/interface/src/starfield/Controller.cpp b/interface/src/starfield/Controller.cpp index b50c393f00..771029c689 100755 --- a/interface/src/starfield/Controller.cpp +++ b/interface/src/starfield/Controller.cpp @@ -10,7 +10,6 @@ // #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/starfield/Generator.cpp b/interface/src/starfield/Generator.cpp index 15d1736e4e..b18e1834be 100644 --- a/interface/src/starfield/Generator.cpp +++ b/interface/src/starfield/Generator.cpp @@ -10,7 +10,6 @@ // #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/ui/BandwidthMeter.h b/interface/src/ui/BandwidthMeter.h index 45226c8e82..6838f28c70 100644 --- a/interface/src/ui/BandwidthMeter.h +++ b/interface/src/ui/BandwidthMeter.h @@ -13,7 +13,6 @@ #define hifi_BandwidthMeter_h #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 5360924e63..77b19373f4 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -79,7 +79,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { shot.setText(ORIENTATION_Z, QString::number(orientation.z)); shot.setText(ORIENTATION_W, QString::number(orientation.w)); - shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainInfo().getHostname()); + shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainHandler().getHostname()); QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z); // replace decimal . with '-' diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index 8d2feb22a2..d946ae5b34 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -20,4 +20,16 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") \ No newline at end of file +link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + +# link GnuTLS +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 420576ece9..001f863e03 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include "AudioRingBuffer.h" diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index 4cc97e56a5..3fa1ebcfe2 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -26,5 +26,15 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -target_link_libraries(${TARGET_NAME} Qt5::Script) \ No newline at end of file +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt index acc060b270..e2a90cb085 100644 --- a/libraries/metavoxels/CMakeLists.txt +++ b/libraries/metavoxels/CMakeLists.txt @@ -20,8 +20,19 @@ auto_mtc(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME} "${AUTOMTC_SRC}") +# link in the networking library +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script) +find_package(GnuTLS REQUIRED) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 9fadef8afc..f1f60e4d87 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -13,7 +13,7 @@ #include -#include +#include #include "DatagramSequencer.h" #include "MetavoxelMessages.h" diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt new file mode 100644 index 0000000000..6d805cae6c --- /dev/null +++ b/libraries/networking/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(ROOT_DIR ../..) +set(MACRO_DIR "${ROOT_DIR}/cmake/macros") + +set(TARGET_NAME networking) +project(${TARGET_NAME}) + +find_package(Qt5 COMPONENTS Network) +find_package(GnuTLS REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +# include GLM +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} "${ROOT_DIR}") + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/shared/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp similarity index 99% rename from libraries/shared/src/AccountManager.cpp rename to libraries/networking/src/AccountManager.cpp index 6c0bcb33cb..d1e4edf2ea 100644 --- a/libraries/shared/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -1,6 +1,6 @@ // // AccountManager.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/AccountManager.h b/libraries/networking/src/AccountManager.h similarity index 99% rename from libraries/shared/src/AccountManager.h rename to libraries/networking/src/AccountManager.h index fed6a61772..cb76786f4e 100644 --- a/libraries/shared/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -1,6 +1,6 @@ // // AccountManager.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp similarity index 99% rename from libraries/shared/src/Assignment.cpp rename to libraries/networking/src/Assignment.cpp index 4d6740b400..dd318aad8e 100644 --- a/libraries/shared/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -1,6 +1,6 @@ // // Assignment.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 8/22/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/Assignment.h b/libraries/networking/src/Assignment.h similarity index 99% rename from libraries/shared/src/Assignment.h rename to libraries/networking/src/Assignment.h index c5a83cf417..041121f760 100644 --- a/libraries/shared/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -1,6 +1,6 @@ // // Assignment.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 8/22/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/networking/src/DTLSClientSession.cpp b/libraries/networking/src/DTLSClientSession.cpp new file mode 100644 index 0000000000..72384fbd10 --- /dev/null +++ b/libraries/networking/src/DTLSClientSession.cpp @@ -0,0 +1,83 @@ +// +// DTLSClientSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DomainHandler.h" + +#include "DTLSClientSession.h" + +gnutls_certificate_credentials_t DTLSClientSession::_x509CACredentials; + +void DTLSClientSession::globalInit() { + static bool initialized = false; + + if (!initialized) { + gnutls_global_init(); + gnutls_certificate_allocate_credentials(&_x509CACredentials); + + gnutls_certificate_set_x509_trust_mem(_x509CACredentials, DTLSSession::highFidelityCADatum(), GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_verify_function(_x509CACredentials, DTLSClientSession::verifyServerCertificate); + } +} + +void DTLSClientSession::globalDeinit() { + gnutls_certificate_free_credentials(_x509CACredentials); + + gnutls_global_deinit(); +} + +int DTLSClientSession::verifyServerCertificate(gnutls_session_t session) { + unsigned int verifyStatus = 0; + + // grab the hostname from the domain handler that this session is associated with + DomainHandler* domainHandler = reinterpret_cast(gnutls_session_get_ptr(session)); + qDebug() << "Checking for" << domainHandler->getHostname() << "from cert."; + + int certReturn = gnutls_certificate_verify_peers3(session, + domainHandler->getHostname().toLocal8Bit().constData(), + &verifyStatus); + + if (certReturn < 0) { + return GNUTLS_E_CERTIFICATE_ERROR; + } + + gnutls_certificate_type_t typeReturn = gnutls_certificate_type_get(session); + + gnutls_datum_t printOut; + + certReturn = gnutls_certificate_verification_status_print(verifyStatus, typeReturn, &printOut, 0); + + if (certReturn < 0) { + return GNUTLS_E_CERTIFICATE_ERROR; + } + + qDebug() << "Gnutls certificate verification status:" << reinterpret_cast(printOut.data); + +#ifdef WIN32 + free(printOut.data); +#else + gnutls_free(printOut.data); +#endif + + if (verifyStatus != 0) { + qDebug() << "Server provided certificate for DTLS is not trusted. Can not complete handshake."; + return GNUTLS_E_CERTIFICATE_ERROR; + } else { + // certificate is valid, continue handshaking as normal + return 0; + } +} + +DTLSClientSession::DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) : + DTLSSession(GNUTLS_CLIENT, dtlsSocket, destinationSocket) +{ + gnutls_priority_set_direct(_gnutlsSession, "PERFORMANCE", NULL); + gnutls_credentials_set(_gnutlsSession, GNUTLS_CRD_CERTIFICATE, _x509CACredentials); +} \ No newline at end of file diff --git a/libraries/networking/src/DTLSClientSession.h b/libraries/networking/src/DTLSClientSession.h new file mode 100644 index 0000000000..eeca1aaa68 --- /dev/null +++ b/libraries/networking/src/DTLSClientSession.h @@ -0,0 +1,30 @@ +// +// DTLSClientSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DTLSClientSession_h +#define hifi_DTLSClientSession_h + +#include "DTLSSession.h" + +class DTLSClientSession : public DTLSSession { +public: + DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); + + static void globalInit(); + static void globalDeinit(); + + static int verifyServerCertificate(gnutls_session_t session); + + static gnutls_certificate_credentials_t _x509CACredentials; + static bool _wasGloballyInitialized; +}; + +#endif // hifi_DTLSClientSession_h diff --git a/libraries/networking/src/DTLSSession.cpp b/libraries/networking/src/DTLSSession.cpp new file mode 100644 index 0000000000..7d375ec327 --- /dev/null +++ b/libraries/networking/src/DTLSSession.cpp @@ -0,0 +1,145 @@ +// +// DTLSSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "NodeList.h" +#include "DTLSSession.h" + +int DTLSSession::socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) { + DTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + + if (dtlsSocket.hasPendingDatagrams()) { + // peek the data on stack to see if it belongs to this session + static sockaddr senderSockAddr; + static socklen_t sockAddrSize = sizeof(senderSockAddr); + + QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0); + + recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(), + MSG_PEEK, &senderSockAddr, &sockAddrSize); + + if (HifiSockAddr(&senderSockAddr) == session->_destinationSocket) { + // there is data for this session ready to be read + return 1; + } else { + // the next data from the dtlsSocket is not for this session + return 0; + } + } else { + // no data available on the dtlsSocket + return 0; + } +} + +ssize_t DTLSSession::socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size) { + DTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + + HifiSockAddr pulledSockAddr; + qint64 bytesReceived = dtlsSocket.readDatagram(reinterpret_cast(buffer), size, + pulledSockAddr.getAddressPointer(), pulledSockAddr.getPortPointer()); + if (bytesReceived == -1) { + // no data to pull, return -1 +#if DTLS_VERBOSE_DEBUG + qDebug() << "Received no data on call to readDatagram"; +#endif + return bytesReceived; + } + + if (pulledSockAddr == session->_destinationSocket) { + // bytes received from the correct sender, return number of bytes received + +#if DTLS_VERBOSE_DEBUG + qDebug() << "Received" << bytesReceived << "on DTLS socket from" << pulledSockAddr; +#endif + + return bytesReceived; + } + + // we pulled a packet not matching this session, so output that + qDebug() << "Denied connection from" << pulledSockAddr; + + gnutls_transport_set_errno(session->_gnutlsSession, GNUTLS_E_AGAIN); + return -1; +} + +gnutls_datum_t* DTLSSession::highFidelityCADatum() { + static gnutls_datum_t hifiCADatum; + static bool datumInitialized = false; + + static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] = + "-----BEGIN CERTIFICATE-----" + "MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n" + "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" + "aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n" + "YXRpb25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEW\n" + "E29wc0BoaWdoZmlkZWxpdHkuaW8wHhcNMTQwMzI4MjIzMzM1WhcNMjQwMzI1MjIz\n" + "MzM1WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\n" + "BAcTDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoTEkhpZ2ggRmlkZWxpdHksIEluYzET\n" + "MBEGA1UECxMKT3BlcmF0aW9uczEYMBYGA1UEAxMPaGlnaGZpZGVsaXR5LmlvMSIw\n" + "IAYJKoZIhvcNAQkBFhNvcHNAaGlnaGZpZGVsaXR5LmlvMIGfMA0GCSqGSIb3DQEB\n" + "AQUAA4GNADCBiQKBgQDyo1euYiPPEdnvDZnIjWrrP230qUKMSj8SWoIkbTJF2hE8\n" + "2eP3YOgbgSGBzZ8EJBxIOuNmj9g9Eg6691hIKFqy5W0BXO38P04Gg+pVBvpHFGBi\n" + "wpqGbfsjaUDuYmBeJRcMO0XYkLCRQG+lAQNHoFDdItWAJfC3FwtP3OCDnz8cNwID\n" + "AQABo4IBEzCCAQ8wHQYDVR0OBBYEFCSv2kmiGg6VFMnxXzLDNP304cPAMIHfBgNV\n" + "HSMEgdcwgdSAFCSv2kmiGg6VFMnxXzLDNP304cPAoYGwpIGtMIGqMQswCQYDVQQG\n" + "EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\n" + "bzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVyYXRp\n" + "b25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEWE29w\n" + "c0BoaWdoZmlkZWxpdHkuaW+CCQDZX0ZEQ/QPGzAMBgNVHRMEBTADAQH/MA0GCSqG\n" + "SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n" + "FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n" + "Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n" + "-----END CERTIFICATE-----"; + + if (!datumInitialized) { + hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT; + hifiCADatum.size = sizeof(HIGHFIDELITY_ROOT_CA_CERT); + } + + return &hifiCADatum; +} + +DTLSSession::DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) : + DummyDTLSSession(dtlsSocket, destinationSocket), + _completedHandshake(false) +{ + gnutls_init(&_gnutlsSession, end | GNUTLS_DATAGRAM | GNUTLS_NONBLOCK); + + // see http://gnutls.org/manual/html_node/Datagram-TLS-API.html#gnutls_005fdtls_005fset_005fmtu + const unsigned int DTLS_MAX_MTU = 1452; + gnutls_dtls_set_mtu(_gnutlsSession, DTLS_MAX_MTU); + + const unsigned int DTLS_HANDSHAKE_RETRANSMISSION_TIMEOUT = DOMAIN_SERVER_CHECK_IN_MSECS; + const unsigned int DTLS_TOTAL_CONNECTION_TIMEOUT = 2 * NODE_SILENCE_THRESHOLD_MSECS; + gnutls_dtls_set_timeouts(_gnutlsSession, DTLS_HANDSHAKE_RETRANSMISSION_TIMEOUT, DTLS_TOTAL_CONNECTION_TIMEOUT); + + gnutls_transport_set_ptr(_gnutlsSession, this); + gnutls_transport_set_push_function(_gnutlsSession, DummyDTLSSession::socketPush); + gnutls_transport_set_pull_function(_gnutlsSession, socketPull); + gnutls_transport_set_pull_timeout_function(_gnutlsSession, socketPullTimeout); +} + +DTLSSession::~DTLSSession() { + gnutls_deinit(_gnutlsSession); +} + +void DTLSSession::setCompletedHandshake(bool completedHandshake) { + _completedHandshake = completedHandshake; + qDebug() << "Completed DTLS handshake with" << _destinationSocket; +} + +qint64 DTLSSession::writeDatagram(const QByteArray& datagram) { + // we don't need to put a hash in this packet, so just send it off + return gnutls_record_send(_gnutlsSession, datagram.data(), datagram.size()); +} \ No newline at end of file diff --git a/libraries/networking/src/DTLSSession.h b/libraries/networking/src/DTLSSession.h new file mode 100644 index 0000000000..9e9542e147 --- /dev/null +++ b/libraries/networking/src/DTLSSession.h @@ -0,0 +1,44 @@ +// +// DTLSSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DTLSSession_h +#define hifi_DTLSSession_h + +#include + +#include + +#include "DummyDTLSSession.h" +#include "HifiSockAddr.h" + +class DTLSSession : public DummyDTLSSession { + Q_OBJECT +public: + DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); + ~DTLSSession(); + + static int socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms); + static ssize_t socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size); + + static gnutls_datum_t* highFidelityCADatum(); + + qint64 writeDatagram(const QByteArray& datagram); + + gnutls_session_t* getGnuTLSSession() { return &_gnutlsSession; } + + bool completedHandshake() const { return _completedHandshake; } + void setCompletedHandshake(bool completedHandshake); +protected: + gnutls_session_t _gnutlsSession; + bool _completedHandshake; +}; + +#endif // hifi_DTLSSession_h diff --git a/libraries/shared/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp similarity index 98% rename from libraries/shared/src/DataServerAccountInfo.cpp rename to libraries/networking/src/DataServerAccountInfo.cpp index 1c53bca30f..87d3b694a7 100644 --- a/libraries/shared/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -1,6 +1,6 @@ // // DataServerAccountInfo.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h similarity index 97% rename from libraries/shared/src/DataServerAccountInfo.h rename to libraries/networking/src/DataServerAccountInfo.h index ae5de6de64..21380c0855 100644 --- a/libraries/shared/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -1,6 +1,6 @@ // // DataServerAccountInfo.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/21/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp new file mode 100644 index 0000000000..6c2fb0274a --- /dev/null +++ b/libraries/networking/src/DomainHandler.cpp @@ -0,0 +1,183 @@ +// +// DomainHandler.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/18/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "NodeList.h" +#include "PacketHeaders.h" + +#include "DomainHandler.h" + +DomainHandler::DomainHandler(QObject* parent) : + QObject(parent), + _uuid(), + _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), + _assignmentUUID(), + _isConnected(false), + _dtlsSession(NULL), + _handshakeTimer(NULL) +{ + +} + +DomainHandler::~DomainHandler() { + delete _dtlsSession; +} + +void DomainHandler::clearConnectionInfo() { + _uuid = QUuid(); + _isConnected = false; + + if (_handshakeTimer) { + _handshakeTimer->stop(); + delete _handshakeTimer; + _handshakeTimer = NULL; + } + + delete _dtlsSession; + _dtlsSession = NULL; +} + +void DomainHandler::reset() { + clearConnectionInfo(); + _hostname = QString(); + _sockAddr.setAddress(QHostAddress::Null); + + delete _dtlsSession; + _dtlsSession = NULL; +} + +const unsigned int DTLS_HANDSHAKE_INTERVAL_MSECS = 100; + +void DomainHandler::initializeDTLSSession() { + if (!_dtlsSession) { + _dtlsSession = new DTLSClientSession(NodeList::getInstance()->getDTLSSocket(), _sockAddr); + + gnutls_session_set_ptr(*_dtlsSession->getGnuTLSSession(), this); + + // start a timer to complete the handshake process + _handshakeTimer = new QTimer(this); + connect(_handshakeTimer, &QTimer::timeout, this, &DomainHandler::completeDTLSHandshake); + + // start the handshake right now + completeDTLSHandshake(); + + // start the timer to finish off the handshake + _handshakeTimer->start(DTLS_HANDSHAKE_INTERVAL_MSECS); + } +} + +void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { + if (_sockAddr != sockAddr) { + // we should reset on a sockAddr change + reset(); + // change the sockAddr + _sockAddr = sockAddr; + } + + // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification + _hostname = hostname; +} + +void DomainHandler::setHostname(const QString& hostname) { + + if (hostname != _hostname) { + // re-set the domain info so that auth information is reloaded + reset(); + + int colonIndex = hostname.indexOf(':'); + + if (colonIndex > 0) { + // the user has included a custom DS port with the hostname + + // the new hostname is everything up to the colon + _hostname = hostname.left(colonIndex); + + // grab the port by reading the string after the colon + _sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData())); + + qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort(); + + } else { + // no port included with the hostname, simply set the member variable and reset the domain server port to default + _hostname = hostname; + _sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT); + } + + // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname + qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); + QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + + emit hostnameChanged(_hostname); + } +} + +void DomainHandler::completeDTLSHandshake() { + int handshakeReturn = gnutls_handshake(*_dtlsSession->getGnuTLSSession()); + + if (handshakeReturn == 0) { + // we've shaken hands, so we're good to go now + _dtlsSession->setCompletedHandshake(true); + + _handshakeTimer->stop(); + delete _handshakeTimer; + _handshakeTimer = NULL; + + // emit a signal so NodeList can handle incoming DTLS packets + emit completedDTLSHandshake(); + + } else if (gnutls_error_is_fatal(handshakeReturn)) { + // this was a fatal error handshaking, so remove this session + qDebug() << "Fatal error -" << gnutls_strerror(handshakeReturn) + << "- during DTLS handshake with DS at" + << qPrintable((_hostname.isEmpty() ? _sockAddr.getAddress().toString() : _hostname)); + + clearConnectionInfo(); + } +} + +void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { + for (int i = 0; i < hostInfo.addresses().size(); i++) { + if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { + _sockAddr.setAddress(hostInfo.addresses()[i]); + qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(), + _sockAddr.getAddress().toString().toLocal8Bit().constData()); + return; + } + } + + // if we got here then we failed to lookup the address + qDebug("Failed domain server lookup"); +} + +void DomainHandler::setIsConnected(bool isConnected) { + if (_isConnected != isConnected) { + _isConnected = isConnected; + + if (_isConnected) { + emit connectedToDomain(_hostname); + } + } +} + +void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) { + // figure out the port that the DS wants us to use for us to talk to them with DTLS + int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket); + + unsigned short dtlsPort = 0; + memcpy(&dtlsPort, dtlsRequirementPacket.data() + numBytesPacketHeader, sizeof(dtlsPort)); + + qDebug() << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS."; + + _sockAddr.setPort(dtlsPort); + + initializeDTLSSession(); +} \ No newline at end of file diff --git a/libraries/shared/src/DomainInfo.h b/libraries/networking/src/DomainHandler.h similarity index 65% rename from libraries/shared/src/DomainInfo.h rename to libraries/networking/src/DomainHandler.h index b52b2f71c3..2cc520991c 100644 --- a/libraries/shared/src/DomainInfo.h +++ b/libraries/networking/src/DomainHandler.h @@ -1,6 +1,6 @@ // -// DomainInfo.h -// libraries/shared/src +// DomainHandler.h +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. @@ -9,28 +9,30 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_DomainInfo_h -#define hifi_DomainInfo_h +#ifndef hifi_DomainHandler_h +#define hifi_DomainHandler_h #include +#include #include #include #include +#include "DTLSClientSession.h" #include "HifiSockAddr.h" const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io"; const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; +const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; -class DomainInfo : public QObject { +class DomainHandler : public QObject { Q_OBJECT public: - DomainInfo(); + DomainHandler(QObject* parent = 0); + ~DomainHandler(); void clearConnectionInfo(); - void parseAuthInformationFromJsonObject(const QJsonObject& jsonObject); - const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } @@ -41,43 +43,40 @@ public: void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } const HifiSockAddr& getSockAddr() { return _sockAddr; } - void setSockAddr(const HifiSockAddr& sockAddr); + void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname); unsigned short getPort() const { return _sockAddr.getPort(); } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - const QUuid& getConnectionSecret() const { return _connectionSecret; } - void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } - - const QByteArray& getRegistrationToken() const { return _registrationToken; } - - const QUrl& getRootAuthenticationURL() const { return _rootAuthenticationURL; } - void setRootAuthenticationURL(const QUrl& rootAuthenticationURL) { _rootAuthenticationURL = rootAuthenticationURL; } - bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); -private slots: - void completedHostnameLookup(const QHostInfo& hostInfo); + DTLSClientSession* getDTLSSession() { return _dtlsSession; } - void logout(); + void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); + +private slots: + void completeDTLSHandshake(); + void completedHostnameLookup(const QHostInfo& hostInfo); signals: void hostnameChanged(const QString& hostname); void connectedToDomain(const QString& hostname); + void completedDTLSHandshake(); + void DTLSConnectionLost(); + private: void reset(); + void initializeDTLSSession(); QUuid _uuid; QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; - QUuid _connectionSecret; - QByteArray _registrationToken; - QUrl _rootAuthenticationURL; - QString _publicKey; bool _isConnected; + DTLSClientSession* _dtlsSession; + QTimer* _handshakeTimer; }; -#endif // hifi_DomainInfo_h +#endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/DummyDTLSSession.cpp b/libraries/networking/src/DummyDTLSSession.cpp new file mode 100644 index 0000000000..a42e3f1599 --- /dev/null +++ b/libraries/networking/src/DummyDTLSSession.cpp @@ -0,0 +1,31 @@ +// +// DummyDTLSSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-04. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DummyDTLSSession.h" + +ssize_t DummyDTLSSession::socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size) { + DummyDTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + +#if DTLS_VERBOSE_DEBUG + qDebug() << "Pushing a message of size" << size << "to" << session->_destinationSocket; +#endif + + return dtlsSocket.writeDatagram(reinterpret_cast(buffer), size, + session->_destinationSocket.getAddress(), session->_destinationSocket.getPort()); +} + +DummyDTLSSession::DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket) : + _dtlsSocket(dtlsSocket), + _destinationSocket(destinationSocket) +{ + +} \ No newline at end of file diff --git a/libraries/networking/src/DummyDTLSSession.h b/libraries/networking/src/DummyDTLSSession.h new file mode 100644 index 0000000000..352b2c23e2 --- /dev/null +++ b/libraries/networking/src/DummyDTLSSession.h @@ -0,0 +1,34 @@ +// +// DummyDTLSSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-04. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DummyDTLSSession_h +#define hifi_DummyDTLSSession_h + +#include + +#include + +#include "HifiSockAddr.h" + +#define DTLS_VERBOSE_DEBUG 0 + +class DummyDTLSSession : public QObject { + Q_OBJECT +public: + DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket); + + static ssize_t socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size); +protected: + QUdpSocket& _dtlsSocket; + HifiSockAddr _destinationSocket; +}; + +#endif // hifi_DummyDTLSSession_h diff --git a/libraries/shared/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp similarity index 76% rename from libraries/shared/src/HifiSockAddr.cpp rename to libraries/networking/src/HifiSockAddr.cpp index 25e9e5637b..d30f7944d7 100644 --- a/libraries/shared/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -1,6 +1,6 @@ // // HifiSockAddr.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 11/26/2013. // Copyright 2013 High Fidelity, Inc. @@ -9,12 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "HifiSockAddr.h" - #include #include #include +#include "HifiSockAddr.h" + static int hifiSockAddrMetaTypeId = qMetaTypeId(); HifiSockAddr::HifiSockAddr() : @@ -47,6 +47,16 @@ HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) { } } +HifiSockAddr::HifiSockAddr(const sockaddr* sockaddr) { + _address = QHostAddress(sockaddr); + + if (sockaddr->sa_family == AF_INET) { + _port = ntohs(reinterpret_cast(sockaddr)->sin_port); + } else { + _port = ntohs(reinterpret_cast(sockaddr)->sin6_port); + } +} + HifiSockAddr& HifiSockAddr::operator=(const HifiSockAddr& rhsSockAddr) { _address = rhsSockAddr._address; _port = rhsSockAddr._port; @@ -85,13 +95,13 @@ quint32 getHostOrderLocalAddress() { static int localAddress = 0; if (localAddress == 0) { - foreach(const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { - if (interface.flags() & QNetworkInterface::IsUp - && interface.flags() & QNetworkInterface::IsRunning - && interface.flags() & ~QNetworkInterface::IsLoopBack) { + foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.flags() & QNetworkInterface::IsUp + && networkInterface.flags() & QNetworkInterface::IsRunning + && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { // we've decided that this is the active NIC // enumerate it's addresses to grab the IPv4 address - foreach(const QNetworkAddressEntry &entry, interface.addressEntries()) { + foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { // make sure it's an IPv4 address that isn't the loopback if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { qDebug("Node's local address is %s", entry.ip().toString().toLocal8Bit().constData()); @@ -112,3 +122,8 @@ quint32 getHostOrderLocalAddress() { // return the looked up local address return localAddress; } + +uint qHash(const HifiSockAddr& key, uint seed) { + // use the existing QHostAddress and quint16 hash functions to get our hash + return qHash(key.getAddress(), seed) + qHash(key.getPort(), seed); +} diff --git a/libraries/shared/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h similarity index 89% rename from libraries/shared/src/HifiSockAddr.h rename to libraries/networking/src/HifiSockAddr.h index f44ebf6dbd..8a591a60b8 100644 --- a/libraries/shared/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -1,6 +1,6 @@ // // HifiSockAddr.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 11/26/2013. // Copyright 2013 High Fidelity, Inc. @@ -12,6 +12,13 @@ #ifndef hifi_HifiSockAddr_h #define hifi_HifiSockAddr_h +#ifdef WIN32 +#include +#include +#else +#include +#endif + #include class HifiSockAddr { @@ -20,6 +27,7 @@ public: HifiSockAddr(const QHostAddress& address, quint16 port); HifiSockAddr(const HifiSockAddr& otherSockAddr); HifiSockAddr(const QString& hostname, quint16 hostOrderPort); + HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } @@ -48,6 +56,8 @@ private: quint16 _port; }; +uint qHash(const HifiSockAddr& key, uint seed); + quint32 getHostOrderLocalAddress(); Q_DECLARE_METATYPE(HifiSockAddr) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp new file mode 100644 index 0000000000..b0b048dde4 --- /dev/null +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -0,0 +1,444 @@ +// +// LimitedNodeList.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "AccountManager.h" +#include "Assignment.h" +#include "HifiSockAddr.h" +#include "Logging.h" +#include "LimitedNodeList.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "UUID.h" + +const char SOLO_NODE_TYPES[2] = { + NodeType::AvatarMixer, + NodeType::AudioMixer +}; + +const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io"); + +LimitedNodeList* LimitedNodeList::_sharedInstance = NULL; + +LimitedNodeList* LimitedNodeList::createInstance(unsigned short socketListenPort, unsigned short dtlsPort) { + if (!_sharedInstance) { + NodeType::init(); + + _sharedInstance = new LimitedNodeList(socketListenPort, dtlsPort); + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + } else { + qDebug("LimitedNodeList createInstance called with existing instance."); + } + + return _sharedInstance; +} + +LimitedNodeList* LimitedNodeList::getInstance() { + if (!_sharedInstance) { + qDebug("LimitedNodeList getInstance called before call to createInstance. Returning NULL pointer."); + } + + return _sharedInstance; +} + + +LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) : + _sessionUUID(), + _nodeHash(), + _nodeHashMutex(QMutex::Recursive), + _nodeSocket(this), + _dtlsSocket(NULL), + _numCollectedPackets(0), + _numCollectedBytes(0), + _packetStatTimer() +{ + _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); + qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); + + if (dtlsListenPort > 0) { + // only create the DTLS socket during constructor if a custom port is passed + _dtlsSocket = new QUdpSocket(this); + + _dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort); + qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); + } + + const int LARGER_SNDBUF_SIZE = 1048576; + changeSendSocketBufferSize(LARGER_SNDBUF_SIZE); + + _packetStatTimer.start(); +} + +void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { + QUuid oldUUID = _sessionUUID; + _sessionUUID = sessionUUID; + + if (sessionUUID != oldUUID) { + qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) + << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); + emit uuidChanged(sessionUUID); + } +} + +QUdpSocket& LimitedNodeList::getDTLSSocket() { + if (!_dtlsSocket) { + // DTLS socket getter called but no DTLS socket exists, create it now + _dtlsSocket = new QUdpSocket(this); + + _dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress); + qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); + } + + return *_dtlsSocket; +} + +void LimitedNodeList::changeSendSocketBufferSize(int numSendBytes) { + // change the socket send buffer size to be 1MB + int oldBufferSize = 0; + +#ifdef Q_OS_WIN + int sizeOfInt = sizeof(oldBufferSize); +#else + unsigned int sizeOfInt = sizeof(oldBufferSize); +#endif + + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&oldBufferSize), &sizeOfInt); + + setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&numSendBytes), + sizeof(numSendBytes)); + + int newBufferSize = 0; + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&newBufferSize), &sizeOfInt); + + qDebug() << "Changed socket send buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; +} + +bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { + PacketType checkType = packetTypeForPacket(packet); + if (packet[1] != versionForPacketType(checkType) + && checkType != PacketTypeStunResponse) { + PacketType mismatchType = packetTypeForPacket(packet); + int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); + + static QMultiMap versionDebugSuppressMap; + + QUuid senderUUID = uuidFromPacketHeader(packet); + if (!versionDebugSuppressMap.contains(senderUUID, checkType)) { + qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" + << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" + << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; + + versionDebugSuppressMap.insert(senderUUID, checkType); + } + + return false; + } + + if (!NON_VERIFIED_PACKETS.contains(checkType)) { + // figure out which node this is from + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + if (sendingNode) { + // check if the md5 hash in the header matches the hash we would expect + if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { + return true; + } else { + qDebug() << "Packet hash mismatch on" << checkType << "- Sender" + << uuidFromPacketHeader(packet); + } + } else { + qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" + << uuidFromPacketHeader(packet); + } + } else { + return true; + } + + return false; +} + +qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, + const QUuid& connectionSecret) { + QByteArray datagramCopy = datagram; + + if (!connectionSecret.isNull()) { + // setup the MD5 hash for source verification in the header + replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); + } + + // stat collection for packets + ++_numCollectedPackets; + _numCollectedBytes += datagram.size(); + + qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + + if (bytesWritten < 0) { + qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); + } + + return bytesWritten; +} + +qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + if (destinationNode) { + // if we don't have an ovveriden address, assume they want to send to the node's active socket + const HifiSockAddr* destinationSockAddr = &overridenSockAddr; + if (overridenSockAddr.isNull()) { + if (destinationNode->getActiveSocket()) { + // use the node's active socket as the destination socket + destinationSockAddr = destinationNode->getActiveSocket(); + } else { + // we don't have a socket to send to, return 0 + return 0; + } + } + + writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret()); + } + + // didn't have a destinationNode to send to, return 0 + return 0; +} + +qint64 LimitedNodeList::writeUnverifiedDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr) { + return writeDatagram(datagram, destinationSockAddr, QUuid()); +} + +qint64 LimitedNodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); +} + +void LimitedNodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { + // the node decided not to do anything with this packet + // if it comes from a known source we should keep that node alive + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + } +} + +int LimitedNodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { + QMutexLocker locker(&matchingNode->getMutex()); + + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + matchingNode->recordBytesReceived(packet.size()); + + if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { + linkedDataCreateCallback(matchingNode.data()); + } + + QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); + + return matchingNode->getLinkedData()->parseData(packet); +} + +int LimitedNodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + + if (matchingNode) { + updateNodeWithDataFromPacket(matchingNode, packet); + } + + // we weren't able to match the sender address to the address we have for this node, unlock and don't parse + return 0; +} + +SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) { + const int WAIT_TIME = 10; // wait up to 10ms in the try lock case + SharedNodePointer node; + // if caller wants us to block and guarantee the correct answer, then honor that request + if (blockingLock) { + // this will block till we can get access + QMutexLocker locker(&_nodeHashMutex); + node = _nodeHash.value(nodeUUID); + } else if (_nodeHashMutex.tryLock(WAIT_TIME)) { // some callers are willing to get wrong answers but not block + node = _nodeHash.value(nodeUUID); + _nodeHashMutex.unlock(); + } + return node; + } + +SharedNodePointer LimitedNodeList::sendingNodeForPacket(const QByteArray& packet) { + QUuid nodeUUID = uuidFromPacketHeader(packet); + + // return the matching node, or NULL if there is no match + return nodeWithUUID(nodeUUID); +} + +NodeHash LimitedNodeList::getNodeHash() { + QMutexLocker locker(&_nodeHashMutex); + return NodeHash(_nodeHash); +} + +void LimitedNodeList::eraseAllNodes() { + qDebug() << "Clearing the NodeList. Deleting all nodes in list."; + + QMutexLocker locker(&_nodeHashMutex); + + NodeHash::iterator nodeItem = _nodeHash.begin(); + + // iterate the nodes in the list + while (nodeItem != _nodeHash.end()) { + nodeItem = killNodeAtHashIterator(nodeItem); + } +} + +void LimitedNodeList::reset() { + eraseAllNodes(); +} + +void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { + QMutexLocker locker(&_nodeHashMutex); + + NodeHash::iterator nodeItemToKill = _nodeHash.find(nodeUUID); + if (nodeItemToKill != _nodeHash.end()) { + killNodeAtHashIterator(nodeItemToKill); + } +} + +NodeHash::iterator LimitedNodeList::killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill) { + qDebug() << "Killed" << *nodeItemToKill.value(); + emit nodeKilled(nodeItemToKill.value()); + return _nodeHash.erase(nodeItemToKill); +} + +void LimitedNodeList::processKillNode(const QByteArray& dataByteArray) { + // read the node id + QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader(dataByteArray), NUM_BYTES_RFC4122_UUID)); + + // kill the node with this UUID, if it exists + killNodeWithUUID(nodeUUID); +} + +SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { + _nodeHashMutex.lock(); + + if (!_nodeHash.contains(uuid)) { + + // we didn't have this node, so add them + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); + SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); + + _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); + + _nodeHashMutex.unlock(); + + qDebug() << "Added" << *newNode; + + emit nodeAdded(newNodeSharedPointer); + + return newNodeSharedPointer; + } else { + _nodeHashMutex.unlock(); + + return updateSocketsForNode(uuid, publicSocket, localSocket); + } +} + +SharedNodePointer LimitedNodeList::updateSocketsForNode(const QUuid& uuid, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { + + SharedNodePointer matchingNode = nodeWithUUID(uuid); + + if (matchingNode) { + // perform appropriate updates to this node + QMutexLocker locker(&matchingNode->getMutex()); + + // check if we need to change this node's public or local sockets + if (publicSocket != matchingNode->getPublicSocket()) { + matchingNode->setPublicSocket(publicSocket); + qDebug() << "Public socket change for node" << *matchingNode; + } + + if (localSocket != matchingNode->getLocalSocket()) { + matchingNode->setLocalSocket(localSocket); + qDebug() << "Local socket change for node" << *matchingNode; + } + } + + return matchingNode; +} + +unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { + unsigned n = 0; + + foreach (const SharedNodePointer& node, getNodeHash()) { + // only send to the NodeTypes we are asked to send to. + if (destinationNodeTypes.contains(node->getType())) { + writeDatagram(packet, node); + ++n; + } + } + + return n; +} + +SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) { + + if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { + foreach (const SharedNodePointer& node, getNodeHash()) { + if (node->getType() == nodeType) { + return node; + } + } + } + return SharedNodePointer(); +} + +void LimitedNodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { + packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); + bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); +} + +void LimitedNodeList::resetPacketStats() { + _numCollectedPackets = 0; + _numCollectedBytes = 0; + _packetStatTimer.restart(); +} + +void LimitedNodeList::removeSilentNodes() { + + _nodeHashMutex.lock(); + + NodeHash::iterator nodeItem = _nodeHash.begin(); + + while (nodeItem != _nodeHash.end()) { + SharedNodePointer node = nodeItem.value(); + + node->getMutex().lock(); + + if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > (NODE_SILENCE_THRESHOLD_MSECS * 1000)) { + // call our private method to kill this node (removes it and emits the right signal) + nodeItem = killNodeAtHashIterator(nodeItem); + } else { + // we didn't kill this node, push the iterator forwards + ++nodeItem; + } + + node->getMutex().unlock(); + } + + _nodeHashMutex.unlock(); +} diff --git a/libraries/shared/src/NodeList.h b/libraries/networking/src/LimitedNodeList.h similarity index 53% rename from libraries/shared/src/NodeList.h rename to libraries/networking/src/LimitedNodeList.h index 86c487e2d6..f941a8aa5d 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -1,6 +1,6 @@ // -// NodeList.h -// libraries/shared/src +// LimitedNodeList.h +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. @@ -9,14 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_NodeList_h -#define hifi_NodeList_h +#ifndef hifi_LimitedNodeList_h +#define hifi_LimitedNodeList_h -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif #include #include @@ -32,12 +27,14 @@ #include #include -#include "DomainInfo.h" +#include + +#include "DomainHandler.h" #include "Node.h" -const quint64 NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000; -const quint64 DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000; -const quint64 PING_INACTIVE_NODE_INTERVAL_USECS = 1 * 1000 * 1000; +const int MAX_PACKET_SIZE = 1500; + +const quint64 NODE_SILENCE_THRESHOLD_MSECS = 2 * 1000; extern const char SOLO_NODE_TYPES[2]; @@ -45,9 +42,6 @@ extern const QUrl DEFAULT_NODE_AUTH_URL; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; -const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; - -class Assignment; class HifiSockAddr; typedef QSet NodeSet; @@ -56,57 +50,31 @@ typedef QSharedPointer SharedNodePointer; typedef QHash NodeHash; Q_DECLARE_METATYPE(SharedNodePointer) -typedef quint8 PingType_t; -namespace PingType { - const PingType_t Agnostic = 0; - const PingType_t Local = 1; - const PingType_t Public = 2; - const PingType_t Symmetric = 3; -} - -class NodeList : public QObject { +class LimitedNodeList : public QObject { Q_OBJECT public: - static NodeList* createInstance(char ownerType, unsigned short int socketListenPort = 0); - static NodeList* getInstance(); - NodeType_t getOwnerType() const { return _ownerType; } - void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } + static LimitedNodeList* createInstance(unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); + static LimitedNodeList* getInstance(); const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); - + QUdpSocket& getNodeSocket() { return _nodeSocket; } + QUdpSocket& getDTLSSocket(); bool packetVersionAndHashMatch(const QByteArray& packet); qint64 writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); + qint64 writeUnverifiedDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr); qint64 writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); - qint64 sendStatsToDomainServer(const QJsonObject& statsObject); void(*linkedDataCreateCallback)(Node *); NodeHash getNodeHash(); int size() const { return _nodeHash.size(); } - int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } - DomainInfo& getDomainInfo() { return _domainInfo; } - - const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; } - void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd); - void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); - void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } - - int processDomainServerList(const QByteArray& packet); - - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } - void sendAssignment(Assignment& assignment); - - QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); - QByteArray constructPingReplyPacket(const QByteArray& pingPacket); - void pingPunchForInactiveNode(const SharedNodePointer& node); - SharedNodePointer nodeWithUUID(const QUuid& nodeUUID, bool blockingLock = true); SharedNodePointer sendingNodeForPacket(const QByteArray& packet); @@ -126,15 +94,10 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); - - void loadData(QSettings* settings); - void saveData(QSettings* settings); public slots: void reset(); void eraseAllNodes(); - void sendDomainServerCheckIn(); - void pingInactiveNodes(); void removeSilentNodes(); void killNodeWithUUID(const QUuid& nodeUUID); @@ -142,45 +105,29 @@ signals: void uuidChanged(const QUuid& ownerUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); - void limitOfSilentDomainCheckInsReached(); -private slots: - void domainServerAuthReply(const QJsonObject& jsonObject); -private: - static NodeList* _sharedInstance; +protected: + static LimitedNodeList* _sharedInstance; - NodeList(char ownerType, unsigned short int socketListenPort); - NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton - void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton - void sendSTUNRequest(); - void processSTUNResponse(const QByteArray& packet); + LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort); + LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton + void operator=(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, const QUuid& connectionSecret); NodeHash::iterator killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill); - void processDomainServerAuthRequest(const QByteArray& packet); - void requestAuthForDomainServer(); - void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); - void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); void changeSendSocketBufferSize(int numSendBytes); + QUuid _sessionUUID; NodeHash _nodeHash; QMutex _nodeHashMutex; QUdpSocket _nodeSocket; - NodeType_t _ownerType; - NodeSet _nodeTypesOfInterest; - DomainInfo _domainInfo; - QUuid _sessionUUID; - int _numNoReplyDomainCheckIns; - HifiSockAddr _assignmentServerSocket; - HifiSockAddr _publicSockAddr; - bool _hasCompletedInitialSTUNFailure; - unsigned int _stunRequestsSinceSuccess; + QUdpSocket* _dtlsSocket; int _numCollectedPackets; int _numCollectedBytes; QElapsedTimer _packetStatTimer; }; -#endif // hifi_NodeList_h +#endif // hifi_LimitedNodeList_h diff --git a/libraries/shared/src/Logging.cpp b/libraries/networking/src/Logging.cpp similarity index 99% rename from libraries/shared/src/Logging.cpp rename to libraries/networking/src/Logging.cpp index 2625cc744a..17a4706de0 100644 --- a/libraries/shared/src/Logging.cpp +++ b/libraries/networking/src/Logging.cpp @@ -1,6 +1,6 @@ // // Logging.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 6/11/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/Logging.h b/libraries/networking/src/Logging.h similarity index 94% rename from libraries/shared/src/Logging.h rename to libraries/networking/src/Logging.h index edbe8f62ee..c52812bd33 100644 --- a/libraries/shared/src/Logging.h +++ b/libraries/networking/src/Logging.h @@ -1,6 +1,6 @@ // // Logging.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 6/11/13. // Copyright 2013 High Fidelity, Inc. @@ -12,12 +12,6 @@ #ifndef hifi_Logging_h #define hifi_Logging_h -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif - #include const int LOGSTASH_UDP_PORT = 9500; diff --git a/libraries/shared/src/NetworkPacket.cpp b/libraries/networking/src/NetworkPacket.cpp similarity index 98% rename from libraries/shared/src/NetworkPacket.cpp rename to libraries/networking/src/NetworkPacket.cpp index b948b10c96..bf18aa9b37 100644 --- a/libraries/shared/src/NetworkPacket.cpp +++ b/libraries/networking/src/NetworkPacket.cpp @@ -1,6 +1,6 @@ // // NetworkPacket.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/9/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/NetworkPacket.h b/libraries/networking/src/NetworkPacket.h similarity index 98% rename from libraries/shared/src/NetworkPacket.h rename to libraries/networking/src/NetworkPacket.h index 6c086e2f5a..94ddf8d56e 100644 --- a/libraries/shared/src/NetworkPacket.h +++ b/libraries/networking/src/NetworkPacket.h @@ -1,6 +1,6 @@ // // NetworkPacket.h -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/9/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/Node.cpp b/libraries/networking/src/Node.cpp similarity index 96% rename from libraries/shared/src/Node.cpp rename to libraries/networking/src/Node.cpp index 13f700ad5a..9e6ea0dfec 100644 --- a/libraries/shared/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -1,6 +1,6 @@ // // Node.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. @@ -12,12 +12,6 @@ #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include // not available on windows, apparently not needed on mac -#endif - #include "Node.h" #include "SharedUtil.h" diff --git a/libraries/shared/src/Node.h b/libraries/networking/src/Node.h similarity index 97% rename from libraries/shared/src/Node.h rename to libraries/networking/src/Node.h index e7ebbcfb13..3b3237cf6e 100644 --- a/libraries/shared/src/Node.h +++ b/libraries/networking/src/Node.h @@ -1,6 +1,6 @@ // // Node.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. @@ -15,12 +15,6 @@ #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif - #include #include #include diff --git a/libraries/shared/src/NodeData.cpp b/libraries/networking/src/NodeData.cpp similarity index 92% rename from libraries/shared/src/NodeData.cpp rename to libraries/networking/src/NodeData.cpp index 6b71ef8f37..2e6fa9e8e1 100644 --- a/libraries/shared/src/NodeData.cpp +++ b/libraries/networking/src/NodeData.cpp @@ -1,6 +1,6 @@ // // NodeData.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/19/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/NodeData.h b/libraries/networking/src/NodeData.h similarity index 95% rename from libraries/shared/src/NodeData.h rename to libraries/networking/src/NodeData.h index 3b26d5b6d5..b865e444a9 100644 --- a/libraries/shared/src/NodeData.h +++ b/libraries/networking/src/NodeData.h @@ -1,6 +1,6 @@ // // NodeData.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/19/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp new file mode 100644 index 0000000000..93377c7763 --- /dev/null +++ b/libraries/networking/src/NodeList.cpp @@ -0,0 +1,590 @@ +// +// NodeList.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include + +#include + +#include "AccountManager.h" +#include "Assignment.h" +#include "HifiSockAddr.h" +#include "Logging.h" +#include "NodeList.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "UUID.h" + +NodeList* NodeList::_sharedInstance = NULL; + +NodeList* NodeList::createInstance(char ownerType, unsigned short socketListenPort, unsigned short dtlsPort) { + if (!_sharedInstance) { + NodeType::init(); + + _sharedInstance = new NodeList(ownerType, socketListenPort, dtlsPort); + LimitedNodeList::_sharedInstance = _sharedInstance; + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + } else { + qDebug("NodeList createInstance called with existing instance."); + } + + return _sharedInstance; +} + +NodeList* NodeList::getInstance() { + if (!_sharedInstance) { + qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); + } + + return _sharedInstance; +} + +NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : + LimitedNodeList(socketListenPort, dtlsListenPort), + _ownerType(newOwnerType), + _nodeTypesOfInterest(), + _domainHandler(this), + _numNoReplyDomainCheckIns(0), + _assignmentServerSocket(), + _publicSockAddr(), + _hasCompletedInitialSTUNFailure(false), + _stunRequestsSinceSuccess(0) +{ + // clear our NodeList when the domain changes + connect(&_domainHandler, &DomainHandler::hostnameChanged, this, &NodeList::reset); + + // clear our NodeList when logout is requested + connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + + // perform a function when DTLS handshake is completed + connect(&_domainHandler, &DomainHandler::completedDTLSHandshake, this, &NodeList::completedDTLSHandshake); +} + +qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { + QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); + QDataStream statsPacketStream(&statsPacket, QIODevice::Append); + + statsPacketStream << statsObject.toVariantMap(); + + return writeDatagram(statsPacket, _domainHandler.getSockAddr(), QUuid()); +} + +void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + quint64 ourOriginalTime, othersReplyTime; + + packetStream >> pingType >> ourOriginalTime >> othersReplyTime; + + quint64 now = usecTimestampNow(); + int pingTime = now - ourOriginalTime; + int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight + + // The other node's expected time should be our original time plus the one way flight time + // anything other than that is clock skew + quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; + int clockSkew = othersReplyTime - othersExprectedReply; + + sendingNode->setPingMs(pingTime / 1000); + sendingNode->setClockSkewUsec(clockSkew); + + const bool wantDebug = false; + + if (wantDebug) { + qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << + " now: " << now << "\n" << + " ourTime: " << ourOriginalTime << "\n" << + " pingTime: " << pingTime << "\n" << + " oneWayFlightTime: " << oneWayFlightTime << "\n" << + " othersReplyTime: " << othersReplyTime << "\n" << + " othersExprectedReply: " << othersExprectedReply << "\n" << + " clockSkew: " << clockSkew; + } +} + +void NodeList::completedDTLSHandshake() { + // at this point, we've got a DTLS socket + // make this NodeList the handler of DTLS packets + connect(_dtlsSocket, &QUdpSocket::readyRead, this, &NodeList::processAvailableDTLSDatagrams); +} + +void NodeList::processAvailableDTLSDatagrams() { + while (_dtlsSocket->hasPendingDatagrams()) { + QByteArray dtlsPacket(_dtlsSocket->pendingDatagramSize(), 0); + + // pull the data from this user off the stack and process it + int receivedBytes = gnutls_record_recv(*_domainHandler.getDTLSSession()->getGnuTLSSession(), + dtlsPacket.data(), dtlsPacket.size()); + if (receivedBytes > 0) { + // successful data receive, hand this off to processNodeData + processNodeData(_domainHandler.getSockAddr(), dtlsPacket.left(receivedBytes)); + } else if (gnutls_error_is_fatal(receivedBytes)) { + qDebug() << "Fatal error -" << gnutls_strerror(receivedBytes) << "- receiving DTLS packet from domain-server."; + } else { + qDebug() << "non fatal receive" << receivedBytes; + } + } +} + +void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { + switch (packetTypeForPacket(packet)) { + case PacketTypeDomainList: { + processDomainServerList(packet); + break; + } + case PacketTypeDomainServerRequireDTLS: { + _domainHandler.parseDTLSRequirementPacket(packet); + break; + } + case PacketTypePing: { + // send back a reply + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + QByteArray replyPacket = constructPingReplyPacket(packet); + writeDatagram(replyPacket, matchingNode, senderSockAddr); + + // If we don't have a symmetric socket for this node and this socket doesn't match + // what we have for public and local then set it as the symmetric. + // This allows a server on a reachable port to communicate with nodes on symmetric NATs + if (matchingNode->getSymmetricSocket().isNull()) { + if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) { + matchingNode->setSymmetricSocket(senderSockAddr); + } + } + } + + break; + } + case PacketTypePingReply: { + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + + if (sendingNode) { + sendingNode->setLastHeardMicrostamp(usecTimestampNow()); + + // activate the appropriate socket for this node, if not yet updated + activateSocketFromNodeCommunication(packet, sendingNode); + + // set the ping time for this node for stat collection + timePingReply(packet, sendingNode); + } + + break; + } + case PacketTypeStunResponse: { + // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch + // pass it along so it can be processed into our public address and port + processSTUNResponse(packet); + break; + } + default: + LimitedNodeList::processNodeData(senderSockAddr, packet); + break; + } +} + +void NodeList::reset() { + LimitedNodeList::reset(); + + _numNoReplyDomainCheckIns = 0; + + // refresh the owner UUID to the NULL UUID + setSessionUUID(QUuid()); + + // clear the domain connection information + _domainHandler.clearConnectionInfo(); + + // also disconnect from the DTLS socket readyRead() so it can handle handshaking + disconnect(_dtlsSocket, 0, this, 0); +} + +void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { + _nodeTypesOfInterest << nodeTypeToAdd; +} + +void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) { + _nodeTypesOfInterest.unite(setOfNodeTypes); +} + +const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; +const int NUM_BYTES_STUN_HEADER = 20; +const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; + +void NodeList::sendSTUNRequest() { + const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; + const unsigned short STUN_SERVER_PORT = 3478; + + unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; + + int packetIndex = 0; + + const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); + + // leading zeros + message type + const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); + memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); + packetIndex += sizeof(REQUEST_MESSAGE_TYPE); + + // message length (no additional attributes are included) + uint16_t messageLength = 0; + memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); + packetIndex += sizeof(messageLength); + + memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); + packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); + + // transaction ID (random 12-byte unsigned integer) + const uint NUM_TRANSACTION_ID_BYTES = 12; + QUuid randomUUID = QUuid::createUuid(); + memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); + + // lookup the IP for the STUN server + static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + + if (!_hasCompletedInitialSTUNFailure) { + qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData()); + } + + _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), + stunSockAddr.getAddress(), stunSockAddr.getPort()); + + _stunRequestsSinceSuccess++; + + if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) { + if (!_hasCompletedInitialSTUNFailure) { + // if we're here this was the last failed STUN request + // use our DS as our stun server + qDebug("Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.", + STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + + _hasCompletedInitialSTUNFailure = true; + } + + // reset the public address and port + // use 0 so the DS knows to act as out STUN server + _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); + } +} + +void NodeList::processSTUNResponse(const QByteArray& packet) { + // check the cookie to make sure this is actually a STUN response + // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS + const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4; + const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020); + + const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); + + int attributeStartIndex = NUM_BYTES_STUN_HEADER; + + if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH, + &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, + sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) { + + // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE + while (attributeStartIndex < packet.size()) { + if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { + const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; + const int NUM_BYTES_FAMILY_ALIGN = 1; + const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; + + // reset the number of failed STUN requests since last success + _stunRequestsSinceSuccess = 0; + + int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; + + uint8_t addressFamily = 0; + memcpy(&addressFamily, packet.data(), sizeof(addressFamily)); + + byteIndex += sizeof(addressFamily); + + if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) { + // grab the X-Port + uint16_t xorMappedPort = 0; + memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort)); + + uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16); + + byteIndex += sizeof(xorMappedPort); + + // grab the X-Address + uint32_t xorMappedAddress = 0; + memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress)); + + uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); + + QHostAddress newPublicAddress = QHostAddress(stunAddress); + + if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { + _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); + + qDebug("New public socket received from STUN server is %s:%hu", + _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), + _publicSockAddr.getPort()); + + } + + _hasCompletedInitialSTUNFailure = true; + + break; + } + } else { + // push forward attributeStartIndex by the length of this attribute + const int NUM_BYTES_ATTRIBUTE_TYPE = 2; + + uint16_t attributeLength = 0; + memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE, + sizeof(attributeLength)); + attributeLength = ntohs(attributeLength); + + attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength; + } + } + } +} + +void NodeList::sendDomainServerCheckIn() { + if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { + // we don't know our public socket and we need to send it to the domain server + // send a STUN request to figure it out + sendSTUNRequest(); + } else if (!_domainHandler.getIP().isNull()) { + + DTLSClientSession* dtlsSession = _domainHandler.getDTLSSession(); + bool isUsingDTLS = false; + + if (dtlsSession) { + if (!dtlsSession->completedHandshake()) { + // if the handshake process is not complete then we can't check in, so return + return; + } else { + isUsingDTLS = true; + } + } + + // construct the DS check in packet + QUuid packetUUID = (!_sessionUUID.isNull() ? _sessionUUID : _domainHandler.getAssignmentUUID()); + + QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest, packetUUID); + QDataStream packetStream(&domainServerPacket, QIODevice::Append); + + // pack our data to send to the domain-server + packetStream << _ownerType << _publicSockAddr + << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) + << (quint8) _nodeTypesOfInterest.size(); + + // copy over the bytes for node types of interest, if required + foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { + packetStream << nodeTypeOfInterest; + } + + if (!isUsingDTLS) { + writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); + } else { + dtlsSession->writeDatagram(domainServerPacket); + } + + + const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; + static unsigned int numDomainCheckins = 0; + + // send a STUN request every Nth domain server check in so we update our public socket, if required + if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { + sendSTUNRequest(); + } + + if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS + // so emit our signal that indicates that + emit limitOfSilentDomainCheckInsReached(); + } + + // increment the count of un-replied check-ins + _numNoReplyDomainCheckIns++; + } +} + +int NodeList::processDomainServerList(const QByteArray& packet) { + // this is a packet from the domain server, reset the count of un-replied check-ins + _numNoReplyDomainCheckIns = 0; + + // if this was the first domain-server list from this domain, we've now connected + _domainHandler.setIsConnected(true); + + int readNodes = 0; + + // setup variables to read into from QDataStream + qint8 nodeType; + + QUuid nodeUUID, connectionUUID; + + HifiSockAddr nodePublicSocket; + HifiSockAddr nodeLocalSocket; + + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + // pull our owner UUID from the packet, it's always the first thing + QUuid newUUID; + packetStream >> newUUID; + setSessionUUID(newUUID); + + // pull each node in the packet + while(packetStream.device()->pos() < packet.size()) { + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; + + // if the public socket address is 0 then it's reachable at the same IP + // as the domain server + if (nodePublicSocket.getAddress().isNull()) { + nodePublicSocket.setAddress(_domainHandler.getIP()); + } + + SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); + + packetStream >> connectionUUID; + node->setConnectionSecret(connectionUUID); + } + + // ping inactive nodes in conjunction with receipt of list from domain-server + // this makes it happen every second and also pings any newly added nodes + pingInactiveNodes(); + + return readNodes; +} + +void NodeList::sendAssignment(Assignment& assignment) { + + PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand + ? PacketTypeCreateAssignment + : PacketTypeRequestAssignment; + + QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); + QDataStream packetStream(&packet, QIODevice::Append); + + packetStream << assignment; + + static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); + + const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() + ? &DEFAULT_ASSIGNMENT_SOCKET + : &_assignmentServerSocket; + + _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); +} + +QByteArray NodeList::constructPingPacket(PingType_t pingType) { + QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); + + QDataStream packetStream(&pingPacket, QIODevice::Append); + + packetStream << pingType; + packetStream << usecTimestampNow(); + + return pingPacket; +} + +QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { + QDataStream pingPacketStream(pingPacket); + pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); + + PingType_t typeFromOriginalPing; + pingPacketStream >> typeFromOriginalPing; + + quint64 timeFromOriginalPing; + pingPacketStream >> timeFromOriginalPing; + + QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); + QDataStream packetStream(&replyPacket, QIODevice::Append); + + packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); + + return replyPacket; +} + +void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { + + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = constructPingPacket(PingType::Local); + writeDatagram(localPingPacket, node, node->getLocalSocket()); + + QByteArray publicPingPacket = constructPingPacket(PingType::Public); + writeDatagram(publicPingPacket, node, node->getPublicSocket()); + + if (!node->getSymmetricSocket().isNull()) { + QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); + writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); + } +} + +void NodeList::pingInactiveNodes() { + foreach (const SharedNodePointer& node, getNodeHash()) { + if (!node->getActiveSocket()) { + // we don't have an active link to this node, ping it to set that up + pingPunchForInactiveNode(node); + } + } +} + +void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { + // deconstruct this ping packet to see if it is a public or local reply + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + packetStream >> pingType; + + // if this is a local or public ping then we can activate a socket + // we do nothing with agnostic pings, those are simply for timing + if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { + sendingNode->activateLocalSocket(); + } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { + sendingNode->activatePublicSocket(); + } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { + sendingNode->activateSymmetricSocket(); + } +} + +const QString QSETTINGS_GROUP_NAME = "NodeList"; +const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; + +void NodeList::loadData(QSettings *settings) { + settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); + + QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); + + if (domainServerHostname.size() > 0) { + _domainHandler.setHostname(domainServerHostname); + } else { + _domainHandler.setHostname(DEFAULT_DOMAIN_HOSTNAME); + } + + settings->endGroup(); +} + +void NodeList::saveData(QSettings* settings) { + settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); + + if (_domainHandler.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { + // the user is using a different hostname, store it + settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainHandler.getHostname())); + } else { + // the user has switched back to default, remove the current setting + settings->remove(DOMAIN_SERVER_SETTING_KEY); + } + + settings->endGroup(); +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h new file mode 100644 index 0000000000..c55f08e7f0 --- /dev/null +++ b/libraries/networking/src/NodeList.h @@ -0,0 +1,114 @@ +// +// NodeList.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_NodeList_h +#define hifi_NodeList_h + +#include +#include + +#ifndef _WIN32 +#include // not on windows, not needed for mac or windows +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DomainHandler.h" +#include "LimitedNodeList.h" +#include "Node.h" + +const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; + +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; + +class Assignment; + +typedef quint8 PingType_t; +namespace PingType { + const PingType_t Agnostic = 0; + const PingType_t Local = 1; + const PingType_t Public = 2; + const PingType_t Symmetric = 3; +} + +class NodeList : public LimitedNodeList { + Q_OBJECT +public: + static NodeList* createInstance(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); + static NodeList* getInstance(); + NodeType_t getOwnerType() const { return _ownerType; } + void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } + + qint64 sendStatsToDomainServer(const QJsonObject& statsObject); + + int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } + DomainHandler& getDomainHandler() { return _domainHandler; } + + const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; } + void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd); + void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); + void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } + + void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet); + + int processDomainServerList(const QByteArray& packet); + + void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } + void sendAssignment(Assignment& assignment); + + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); + QByteArray constructPingReplyPacket(const QByteArray& pingPacket); + + void pingPunchForInactiveNode(const SharedNodePointer& node); + + void loadData(QSettings* settings); + void saveData(QSettings* settings); +public slots: + void reset(); + void sendDomainServerCheckIn(); + void pingInactiveNodes(); + void completedDTLSHandshake(); + void processAvailableDTLSDatagrams(); +signals: + void limitOfSilentDomainCheckInsReached(); +private: + static NodeList* _sharedInstance; + + NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort); + NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton + void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton + void sendSTUNRequest(); + void processSTUNResponse(const QByteArray& packet); + + void processDomainServerAuthRequest(const QByteArray& packet); + void requestAuthForDomainServer(); + void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); + void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); + + NodeType_t _ownerType; + NodeSet _nodeTypesOfInterest; + DomainHandler _domainHandler; + int _numNoReplyDomainCheckIns; + HifiSockAddr _assignmentServerSocket; + HifiSockAddr _publicSockAddr; + bool _hasCompletedInitialSTUNFailure; + unsigned int _stunRequestsSinceSuccess; +}; + +#endif // hifi_NodeList_h diff --git a/libraries/shared/src/OAuthAccessToken.cpp b/libraries/networking/src/OAuthAccessToken.cpp similarity index 98% rename from libraries/shared/src/OAuthAccessToken.cpp rename to libraries/networking/src/OAuthAccessToken.cpp index ab5ec6462a..365b07a935 100644 --- a/libraries/shared/src/OAuthAccessToken.cpp +++ b/libraries/networking/src/OAuthAccessToken.cpp @@ -1,6 +1,6 @@ // // OAuthAccessToken.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/OAuthAccessToken.h b/libraries/networking/src/OAuthAccessToken.h similarity index 97% rename from libraries/shared/src/OAuthAccessToken.h rename to libraries/networking/src/OAuthAccessToken.h index 7f7c621231..36859b79f8 100644 --- a/libraries/shared/src/OAuthAccessToken.h +++ b/libraries/networking/src/OAuthAccessToken.h @@ -1,6 +1,6 @@ // // OAuthAccessToken.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp similarity index 81% rename from libraries/shared/src/PacketHeaders.cpp rename to libraries/networking/src/PacketHeaders.cpp index 2832fde56e..fdbdca3db7 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -1,6 +1,6 @@ // // PacketHeaders.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 6/28/13. // Copyright 2013 High Fidelity, Inc. @@ -55,15 +55,10 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeDomainList: case PacketTypeDomainListRequest: - return 1; + return 2; case PacketTypeCreateAssignment: case PacketTypeRequestAssignment: - return 1; - case PacketTypeDataServerGet: - case PacketTypeDataServerPut: - case PacketTypeDataServerConfirm: - case PacketTypeDataServerSend: - return 1; + return 2; case PacketTypeVoxelSet: case PacketTypeVoxelSetDestructive: return 1; @@ -92,15 +87,17 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU char* position = packet + numTypeBytes + sizeof(PacketVersion); - QUuid packUUID = connectionUUID.isNull() ? NodeList::getInstance()->getSessionUUID() : connectionUUID; + QUuid packUUID = connectionUUID.isNull() ? LimitedNodeList::getInstance()->getSessionUUID() : connectionUUID; QByteArray rfcUUID = packUUID.toRfc4122(); memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); position += NUM_BYTES_RFC4122_UUID; - // pack 16 bytes of zeros where the md5 hash will be placed one data is packed - memset(position, 0, NUM_BYTES_MD5_HASH); - position += NUM_BYTES_MD5_HASH; + if (!NON_VERIFIED_PACKETS.contains(type)) { + // pack 16 bytes of zeros where the md5 hash will be placed one data is packed + memset(position, 0, NUM_BYTES_MD5_HASH); + position += NUM_BYTES_MD5_HASH; + } // return the number of bytes written for pointer pushing return position - packet; @@ -108,16 +105,26 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU int numBytesForPacketHeader(const QByteArray& packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet.data()) + NUM_STATIC_HEADER_BYTES; + return numBytesArithmeticCodingFromBuffer(packet.data()) + + numHashBytesInPacketHeaderGivenPacketType(packetTypeForPacket(packet)) + + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeader(const char* packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet) + NUM_STATIC_HEADER_BYTES; + return numBytesArithmeticCodingFromBuffer(packet) + + numHashBytesInPacketHeaderGivenPacketType(packetTypeForPacket(packet)) + + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeaderGivenPacketType(PacketType type) { - return (int) ceilf((float)type / 255) + NUM_STATIC_HEADER_BYTES; + return (int) ceilf((float)type / 255) + + numHashBytesInPacketHeaderGivenPacketType(type) + + NUM_STATIC_HEADER_BYTES; +} + +int numHashBytesInPacketHeaderGivenPacketType(PacketType type) { + return (NON_VERIFIED_PACKETS.contains(type) ? 0 : NUM_BYTES_MD5_HASH); } QUuid uuidFromPacketHeader(const QByteArray& packet) { diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h similarity index 78% rename from libraries/shared/src/PacketHeaders.h rename to libraries/networking/src/PacketHeaders.h index 3183e8f12d..3e2ff3d8b0 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -1,6 +1,6 @@ // // PacketHeaders.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 4/8/13. // Copyright 2013 High Fidelity, Inc. @@ -13,6 +13,7 @@ #define hifi_PacketHeaders_h #include +#include #include #include "UUID.h" @@ -37,9 +38,9 @@ enum PacketType { PacketTypeDomainListRequest, PacketTypeRequestAssignment, PacketTypeCreateAssignment, - PacketTypeDataServerPut, - PacketTypeDataServerGet, - PacketTypeDataServerSend, + PacketTypeDataServerPut, // reusable + PacketTypeDataServerGet, // reusable + PacketTypeDataServerSend, // reusable PacketTypeDataServerConfirm, PacketTypeVoxelQuery, PacketTypeVoxelData, @@ -57,16 +58,21 @@ enum PacketType { PacketTypeMetavoxelData, PacketTypeAvatarIdentity, PacketTypeAvatarBillboard, - PacketTypeDomainConnectRequest, - PacketTypeDomainServerAuthRequest, - PacketTypeNodeJsonStats + PacketTypeDomainConnectRequest, // reusable + PacketTypeDomainServerRequireDTLS, + PacketTypeNodeJsonStats, }; typedef char PacketVersion; +const QSet NON_VERIFIED_PACKETS = QSet() + << PacketTypeDomainServerRequireDTLS << PacketTypeDomainList << PacketTypeDomainListRequest + << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse + << PacketTypeNodeJsonStats; + const int NUM_BYTES_MD5_HASH = 16; -const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; -const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_STATIC_HEADER_BYTES; +const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; +const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_BYTES_MD5_HASH + NUM_STATIC_HEADER_BYTES; PacketVersion versionForPacketType(PacketType type); @@ -76,6 +82,8 @@ QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connection int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID); int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID); +int numHashBytesInPacketHeaderGivenPacketType(PacketType type); + int numBytesForPacketHeader(const QByteArray& packet); int numBytesForPacketHeader(const char* packet); int numBytesForPacketHeaderGivenPacketType(PacketType type); diff --git a/libraries/shared/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp similarity index 99% rename from libraries/shared/src/PacketSender.cpp rename to libraries/networking/src/PacketSender.cpp index 489b083d92..5f7502a738 100644 --- a/libraries/shared/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -1,6 +1,6 @@ // // PacketSender.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/PacketSender.h b/libraries/networking/src/PacketSender.h similarity index 99% rename from libraries/shared/src/PacketSender.h rename to libraries/networking/src/PacketSender.h index cc65564461..7d2c0dc8aa 100644 --- a/libraries/shared/src/PacketSender.h +++ b/libraries/networking/src/PacketSender.h @@ -1,6 +1,6 @@ // // PacketSender.h -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp similarity index 98% rename from libraries/shared/src/ReceivedPacketProcessor.cpp rename to libraries/networking/src/ReceivedPacketProcessor.cpp index 8394559524..d54e165285 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -1,6 +1,6 @@ // // ReceivedPacketProcessor.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h similarity index 98% rename from libraries/shared/src/ReceivedPacketProcessor.h rename to libraries/networking/src/ReceivedPacketProcessor.h index a34b43626c..f8306b4896 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -1,6 +1,6 @@ // // ReceivedPacketProcessor.h -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp similarity index 95% rename from libraries/shared/src/ThreadedAssignment.cpp rename to libraries/networking/src/ThreadedAssignment.cpp index 1bd79895ab..4b92f8ba38 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -44,11 +44,11 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeRemovalTimer = new QTimer(this); connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_MSECS); if (shouldSendStats) { // send a stats packet every 1 second diff --git a/libraries/shared/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h similarity index 100% rename from libraries/shared/src/ThreadedAssignment.h rename to libraries/networking/src/ThreadedAssignment.h diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index 422089a57d..88554ed5f8 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -22,9 +22,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/particles/CMakeLists.txt b/libraries/particles/CMakeLists.txt index b483e3e479..26d2b7fc26 100644 --- a/libraries/particles/CMakeLists.txt +++ b/libraries/particles/CMakeLists.txt @@ -23,9 +23,16 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 615000e260..20569e2fe0 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -28,6 +28,12 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") # link ZLIB find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets) \ No newline at end of file diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 3af7272cc1..7f9a34492d 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -32,4 +32,4 @@ if (UNIX AND NOT APPLE) target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") endif (UNIX AND NOT APPLE) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) \ No newline at end of file diff --git a/libraries/shared/src/DomainInfo.cpp b/libraries/shared/src/DomainInfo.cpp deleted file mode 100644 index 34498f4c50..0000000000 --- a/libraries/shared/src/DomainInfo.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// -// DomainInfo.cpp -// libraries/shared/src -// -// Created by Stephen Birarda on 2/18/2014. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include "AccountManager.h" - -#include "DomainInfo.h" - -DomainInfo::DomainInfo() : - _uuid(), - _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), - _assignmentUUID(), - _connectionSecret(), - _registrationToken(), - _rootAuthenticationURL(), - _publicKey(), - _isConnected(false) -{ - // clear appropriate variables after a domain-server logout - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete, this, &DomainInfo::logout); -} - -void DomainInfo::clearConnectionInfo() { - _uuid = QUuid(); - _connectionSecret = QUuid(); - _registrationToken = QByteArray(); - _rootAuthenticationURL = QUrl(); - _publicKey = QString(); - _isConnected = false; -} - -void DomainInfo::reset() { - clearConnectionInfo(); - _hostname = QString(); - _sockAddr.setAddress(QHostAddress::Null); -} - -void DomainInfo::parseAuthInformationFromJsonObject(const QJsonObject& jsonObject) { - QJsonObject dataObject = jsonObject["data"].toObject(); - _connectionSecret = QUuid(dataObject["connection_secret"].toString()); - _registrationToken = QByteArray::fromHex(dataObject["registration_token"].toString().toUtf8()); - _publicKey = dataObject["public_key"].toString(); -} - -void DomainInfo::setSockAddr(const HifiSockAddr& sockAddr) { - if (_sockAddr != sockAddr) { - // we should reset on a sockAddr change - reset(); - // change the sockAddr - _sockAddr = sockAddr; - } -} - -void DomainInfo::setHostname(const QString& hostname) { - - if (hostname != _hostname) { - // re-set the domain info so that auth information is reloaded - reset(); - - int colonIndex = hostname.indexOf(':'); - - if (colonIndex > 0) { - // the user has included a custom DS port with the hostname - - // the new hostname is everything up to the colon - _hostname = hostname.left(colonIndex); - - // grab the port by reading the string after the colon - _sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData())); - - qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort(); - - } else { - // no port included with the hostname, simply set the member variable and reset the domain server port to default - _hostname = hostname; - _sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT); - } - - // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname - qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); - QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); - - emit hostnameChanged(_hostname); - } -} - -void DomainInfo::completedHostnameLookup(const QHostInfo& hostInfo) { - for (int i = 0; i < hostInfo.addresses().size(); i++) { - if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { - _sockAddr.setAddress(hostInfo.addresses()[i]); - qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(), - _sockAddr.getAddress().toString().toLocal8Bit().constData()); - return; - } - } - - // if we got here then we failed to lookup the address - qDebug("Failed domain server lookup"); -} - -void DomainInfo::setIsConnected(bool isConnected) { - if (_isConnected != isConnected) { - _isConnected = isConnected; - - if (_isConnected) { - emit connectedToDomain(_hostname); - } - } -} - -void DomainInfo::logout() { - // clear any information related to auth for this domain, assuming it had requested auth - if (!_rootAuthenticationURL.isEmpty()) { - _rootAuthenticationURL = QUrl(); - _connectionSecret = QUuid(); - _registrationToken = QByteArray(); - _publicKey = QString(); - _isConnected = false; - } -} diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp new file mode 100644 index 0000000000..d20f4276f1 --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -0,0 +1,98 @@ +// +// HifiConfigVariantMap.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-04-08. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include + +#include "HifiConfigVariantMap.h" + +QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringList& argumentList) { + + QVariantMap mergedMap; + + // Add anything in the CL parameter list to the variant map. + // Take anything with a dash in it as a key, and the values after it as the value. + + const QString DASHED_KEY_REGEX_STRING = "(^-{1,2})([\\w-]+)"; + QRegExp dashedKeyRegex(DASHED_KEY_REGEX_STRING); + + int keyIndex = argumentList.indexOf(dashedKeyRegex); + int nextKeyIndex = 0; + + // check if there is a config file to read where we can pull config info not passed on command line + const QString CONFIG_FILE_OPTION = "--config"; + + while (keyIndex != -1) { + if (argumentList[keyIndex] != CONFIG_FILE_OPTION) { + // we have a key - look forward to see how many values associate to it + QString key = dashedKeyRegex.cap(2); + + nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + + if (nextKeyIndex == keyIndex + 1) { + // there's no value associated with this option, it's a boolean + // so add it to the variant map with NULL as value + mergedMap.insertMulti(key, QVariant()); + } else { + int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex; + + // there's at least one value associated with the option + // pull the first value to start + QString value = argumentList[keyIndex + 1]; + + // for any extra values, append them, with a space, to the value string + for (int i = keyIndex + 2; i < maxIndex; i++) { + value += " " + argumentList[i]; + } + + // add the finalized value to the merged map + mergedMap.insert(key, value); + } + + keyIndex = nextKeyIndex; + } else { + keyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + } + } + + int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION); + + if (configIndex != -1) { + // we have a config file - try and read it + QString configFilePath = argumentList[configIndex + 1]; + QFile configFile(configFilePath); + + if (configFile.exists()) { + qDebug() << "Reading JSON config file at" << configFilePath; + configFile.open(QIODevice::ReadOnly); + + QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll()); + QJsonObject rootObject = configDocument.object(); + + // enumerate the keys of the configDocument object + foreach(const QString& key, rootObject.keys()) { + + if (!mergedMap.contains(key)) { + // no match in existing list, add it + mergedMap.insert(key, QVariant(rootObject[key])); + } + } + } else { + qDebug() << "Could not find JSON config file at" << configFilePath; + } + } + + return mergedMap; +} diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h new file mode 100644 index 0000000000..378aa749c5 --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -0,0 +1,22 @@ +// +// HifiConfigVariantMap.h +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-04-08. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_HifiConfigVariantMap_h +#define hifi_HifiConfigVariantMap_h + +#include + +class HifiConfigVariantMap { +public: + static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); +}; + +#endif // hifi_HifiConfigVariantMap_h diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp deleted file mode 100644 index 7ca6202398..0000000000 --- a/libraries/shared/src/NodeList.cpp +++ /dev/null @@ -1,981 +0,0 @@ -// -// NodeList.cpp -// libraries/shared/src -// -// Created by Stephen Birarda on 2/15/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "AccountManager.h" -#include "Assignment.h" -#include "HifiSockAddr.h" -#include "Logging.h" -#include "NodeList.h" -#include "PacketHeaders.h" -#include "SharedUtil.h" -#include "UUID.h" - -const char SOLO_NODE_TYPES[2] = { - NodeType::AvatarMixer, - NodeType::AudioMixer -}; - -const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io"); - -NodeList* NodeList::_sharedInstance = NULL; - -NodeList* NodeList::createInstance(char ownerType, unsigned short int socketListenPort) { - if (!_sharedInstance) { - NodeType::init(); - - _sharedInstance = new NodeList(ownerType, socketListenPort); - - // register the SharedNodePointer meta-type for signals/slots - qRegisterMetaType(); - } else { - qDebug("NodeList createInstance called with existing instance."); - } - - return _sharedInstance; -} - -NodeList* NodeList::getInstance() { - if (!_sharedInstance) { - qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); - } - - return _sharedInstance; -} - - -NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : - _nodeHash(), - _nodeHashMutex(QMutex::Recursive), - _nodeSocket(this), - _ownerType(newOwnerType), - _nodeTypesOfInterest(), - _sessionUUID(), - _numNoReplyDomainCheckIns(0), - _assignmentServerSocket(), - _publicSockAddr(), - _hasCompletedInitialSTUNFailure(false), - _stunRequestsSinceSuccess(0), - _numCollectedPackets(0), - _numCollectedBytes(0), - _packetStatTimer() -{ - _nodeSocket.bind(QHostAddress::AnyIPv4, newSocketListenPort); - qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); - - // clear our NodeList when the domain changes - connect(&_domainInfo, &DomainInfo::hostnameChanged, this, &NodeList::reset); - - // clear our NodeList when logout is requested - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); - - const int LARGER_SNDBUF_SIZE = 1048576; - changeSendSocketBufferSize(LARGER_SNDBUF_SIZE); - - _packetStatTimer.start(); -} - -void NodeList::changeSendSocketBufferSize(int numSendBytes) { - // change the socket send buffer size to be 1MB - int oldBufferSize = 0; - -#ifdef Q_OS_WIN - int sizeOfInt = sizeof(oldBufferSize); -#else - unsigned int sizeOfInt = sizeof(oldBufferSize); -#endif - - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&oldBufferSize), &sizeOfInt); - - setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&numSendBytes), - sizeof(numSendBytes)); - - int newBufferSize = 0; - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&newBufferSize), &sizeOfInt); - - qDebug() << "Changed socket send buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; -} - -bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) { - PacketType checkType = packetTypeForPacket(packet); - if (packet[1] != versionForPacketType(checkType) - && checkType != PacketTypeStunResponse) { - PacketType mismatchType = packetTypeForPacket(packet); - int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); - - static QMultiMap versionDebugSuppressMap; - - QUuid senderUUID = uuidFromPacketHeader(packet); - if (!versionDebugSuppressMap.contains(senderUUID, checkType)) { - qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" - << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" - << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; - - versionDebugSuppressMap.insert(senderUUID, checkType); - } - - return false; - } - - const QSet NON_VERIFIED_PACKETS = QSet() - << PacketTypeDomainServerAuthRequest << PacketTypeDomainConnectRequest - << PacketTypeStunResponse << PacketTypeDataServerConfirm - << PacketTypeDataServerGet << PacketTypeDataServerPut << PacketTypeDataServerSend - << PacketTypeCreateAssignment << PacketTypeRequestAssignment; - - if (!NON_VERIFIED_PACKETS.contains(checkType)) { - // figure out which node this is from - SharedNodePointer sendingNode = sendingNodeForPacket(packet); - if (sendingNode) { - // check if the md5 hash in the header matches the hash we would expect - if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { - return true; - } else { - qDebug() << "Packet hash mismatch on" << checkType << "- Sender" - << uuidFromPacketHeader(packet); - } - } else { - if (checkType == PacketTypeDomainList) { - - if (_domainInfo.getRootAuthenticationURL().isEmpty() && _domainInfo.getUUID().isNull()) { - // if this is a domain-server that doesn't require auth, - // pull the UUID from this packet and set it as our domain-server UUID - _domainInfo.setUUID(uuidFromPacketHeader(packet)); - - // we also know this domain-server requires no authentication - // so set the account manager root URL to the default one - AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL); - } - - if (_domainInfo.getUUID() == uuidFromPacketHeader(packet)) { - if (hashForPacketAndConnectionUUID(packet, _domainInfo.getConnectionSecret()) == hashFromPacketHeader(packet)) { - // this is a packet from the domain-server (PacketTypeDomainServerListRequest) - // and the sender UUID matches the UUID we expect for the domain - return true; - } else { - // this is a packet from the domain-server but there is a hash mismatch - qDebug() << "Packet hash mismatch on" << checkType << "from domain-server at" << _domainInfo.getHostname(); - return false; - } - } - } - - qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" - << uuidFromPacketHeader(packet); - } - } else { - return true; - } - - return false; -} - -qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, - const QUuid& connectionSecret) { - QByteArray datagramCopy = datagram; - - // setup the MD5 hash for source verification in the header - replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); - - // stat collection for packets - ++_numCollectedPackets; - _numCollectedBytes += datagram.size(); - - qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - - if (bytesWritten < 0) { - qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); - } - - return bytesWritten; -} - -qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, - const HifiSockAddr& overridenSockAddr) { - if (destinationNode) { - // if we don't have an ovveriden address, assume they want to send to the node's active socket - const HifiSockAddr* destinationSockAddr = &overridenSockAddr; - if (overridenSockAddr.isNull()) { - if (destinationNode->getActiveSocket()) { - // use the node's active socket as the destination socket - destinationSockAddr = destinationNode->getActiveSocket(); - } else { - // we don't have a socket to send to, return 0 - return 0; - } - } - - writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret()); - } - - // didn't have a destinationNode to send to, return 0 - return 0; -} - -qint64 NodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, - const HifiSockAddr& overridenSockAddr) { - return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); -} - -qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { - QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); - QDataStream statsPacketStream(&statsPacket, QIODevice::Append); - - statsPacketStream << statsObject.toVariantMap(); - - return writeDatagram(statsPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); -} - -void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint8 pingType; - quint64 ourOriginalTime, othersReplyTime; - - packetStream >> pingType >> ourOriginalTime >> othersReplyTime; - - quint64 now = usecTimestampNow(); - int pingTime = now - ourOriginalTime; - int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight - - // The other node's expected time should be our original time plus the one way flight time - // anything other than that is clock skew - quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; - int clockSkew = othersReplyTime - othersExprectedReply; - - sendingNode->setPingMs(pingTime / 1000); - sendingNode->setClockSkewUsec(clockSkew); - - const bool wantDebug = false; - - if (wantDebug) { - qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << - " now: " << now << "\n" << - " ourTime: " << ourOriginalTime << "\n" << - " pingTime: " << pingTime << "\n" << - " oneWayFlightTime: " << oneWayFlightTime << "\n" << - " othersReplyTime: " << othersReplyTime << "\n" << - " othersExprectedReply: " << othersExprectedReply << "\n" << - " clockSkew: " << clockSkew; - } -} - -void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { - switch (packetTypeForPacket(packet)) { - case PacketTypeDomainList: { - processDomainServerList(packet); - break; - } - case PacketTypeDomainServerAuthRequest: { - // the domain-server has asked us to auth via a data-server - processDomainServerAuthRequest(packet); - - break; - } - case PacketTypePing: { - // send back a reply - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - if (matchingNode) { - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - QByteArray replyPacket = constructPingReplyPacket(packet); - writeDatagram(replyPacket, matchingNode, senderSockAddr); - - // If we don't have a symmetric socket for this node and this socket doesn't match - // what we have for public and local then set it as the symmetric. - // This allows a server on a reachable port to communicate with nodes on symmetric NATs - if (matchingNode->getSymmetricSocket().isNull()) { - if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) { - matchingNode->setSymmetricSocket(senderSockAddr); - } - } - } - - break; - } - case PacketTypePingReply: { - SharedNodePointer sendingNode = sendingNodeForPacket(packet); - - if (sendingNode) { - sendingNode->setLastHeardMicrostamp(usecTimestampNow()); - - // activate the appropriate socket for this node, if not yet updated - activateSocketFromNodeCommunication(packet, sendingNode); - - // set the ping time for this node for stat collection - timePingReply(packet, sendingNode); - } - - break; - } - case PacketTypeStunResponse: { - // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch - // pass it along so it can be processed into our public address and port - processSTUNResponse(packet); - break; - } - default: - // the node decided not to do anything with this packet - // if it comes from a known source we should keep that node alive - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - if (matchingNode) { - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - } - - break; - } -} - -int NodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { - QMutexLocker locker(&matchingNode->getMutex()); - - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - matchingNode->recordBytesReceived(packet.size()); - - if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { - linkedDataCreateCallback(matchingNode.data()); - } - - QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); - - return matchingNode->getLinkedData()->parseData(packet); -} - -int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - - if (matchingNode) { - updateNodeWithDataFromPacket(matchingNode, packet); - } - - // we weren't able to match the sender address to the address we have for this node, unlock and don't parse - return 0; -} - -SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) { - const int WAIT_TIME = 10; // wait up to 10ms in the try lock case - SharedNodePointer node; - // if caller wants us to block and guarantee the correct answer, then honor that request - if (blockingLock) { - // this will block till we can get access - QMutexLocker locker(&_nodeHashMutex); - node = _nodeHash.value(nodeUUID); - } else if (_nodeHashMutex.tryLock(WAIT_TIME)) { // some callers are willing to get wrong answers but not block - node = _nodeHash.value(nodeUUID); - _nodeHashMutex.unlock(); - } - return node; - } - -SharedNodePointer NodeList::sendingNodeForPacket(const QByteArray& packet) { - QUuid nodeUUID = uuidFromPacketHeader(packet); - - // return the matching node, or NULL if there is no match - return nodeWithUUID(nodeUUID); -} - -NodeHash NodeList::getNodeHash() { - QMutexLocker locker(&_nodeHashMutex); - return NodeHash(_nodeHash); -} - -void NodeList::eraseAllNodes() { - qDebug() << "Clearing the NodeList. Deleting all nodes in list."; - - QMutexLocker locker(&_nodeHashMutex); - - NodeHash::iterator nodeItem = _nodeHash.begin(); - - // iterate the nodes in the list - while (nodeItem != _nodeHash.end()) { - nodeItem = killNodeAtHashIterator(nodeItem); - } -} - -void NodeList::reset() { - eraseAllNodes(); - _numNoReplyDomainCheckIns = 0; - - // refresh the owner UUID to the NULL UUID - setSessionUUID(QUuid()); - - // clear the domain connection information - _domainInfo.clearConnectionInfo(); -} - -void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { - _nodeTypesOfInterest << nodeTypeToAdd; -} - -void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) { - _nodeTypesOfInterest.unite(setOfNodeTypes); -} - -const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; -const int NUM_BYTES_STUN_HEADER = 20; -const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; - -void NodeList::sendSTUNRequest() { - const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; - const unsigned short STUN_SERVER_PORT = 3478; - - unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; - - int packetIndex = 0; - - const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - - // leading zeros + message type - const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); - memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); - packetIndex += sizeof(REQUEST_MESSAGE_TYPE); - - // message length (no additional attributes are included) - uint16_t messageLength = 0; - memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); - packetIndex += sizeof(messageLength); - - memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); - packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - // transaction ID (random 12-byte unsigned integer) - const uint NUM_TRANSACTION_ID_BYTES = 12; - QUuid randomUUID = QUuid::createUuid(); - memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); - - // lookup the IP for the STUN server - static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - if (!_hasCompletedInitialSTUNFailure) { - qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData()); - } - - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - stunSockAddr.getAddress(), stunSockAddr.getPort()); - - _stunRequestsSinceSuccess++; - - if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) { - if (!_hasCompletedInitialSTUNFailure) { - // if we're here this was the last failed STUN request - // use our DS as our stun server - qDebug("Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.", - STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - _hasCompletedInitialSTUNFailure = true; - } - - // reset the public address and port - // use 0 so the DS knows to act as out STUN server - _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); - } -} - -void NodeList::processSTUNResponse(const QByteArray& packet) { - // check the cookie to make sure this is actually a STUN response - // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS - const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4; - const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020); - - const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - - int attributeStartIndex = NUM_BYTES_STUN_HEADER; - - if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH, - &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, - sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) { - - // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE - while (attributeStartIndex < packet.size()) { - if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { - const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; - const int NUM_BYTES_FAMILY_ALIGN = 1; - const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; - - // reset the number of failed STUN requests since last success - _stunRequestsSinceSuccess = 0; - - int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; - - uint8_t addressFamily = 0; - memcpy(&addressFamily, packet.data(), sizeof(addressFamily)); - - byteIndex += sizeof(addressFamily); - - if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) { - // grab the X-Port - uint16_t xorMappedPort = 0; - memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort)); - - uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16); - - byteIndex += sizeof(xorMappedPort); - - // grab the X-Address - uint32_t xorMappedAddress = 0; - memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress)); - - uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - QHostAddress newPublicAddress = QHostAddress(stunAddress); - - if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { - _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); - - qDebug("New public socket received from STUN server is %s:%hu", - _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), - _publicSockAddr.getPort()); - - } - - _hasCompletedInitialSTUNFailure = true; - - break; - } - } else { - // push forward attributeStartIndex by the length of this attribute - const int NUM_BYTES_ATTRIBUTE_TYPE = 2; - - uint16_t attributeLength = 0; - memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE, - sizeof(attributeLength)); - attributeLength = ntohs(attributeLength); - - attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength; - } - } - } -} - -void NodeList::killNodeWithUUID(const QUuid& nodeUUID) { - QMutexLocker locker(&_nodeHashMutex); - - NodeHash::iterator nodeItemToKill = _nodeHash.find(nodeUUID); - if (nodeItemToKill != _nodeHash.end()) { - killNodeAtHashIterator(nodeItemToKill); - } -} - -NodeHash::iterator NodeList::killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill) { - qDebug() << "Killed" << *nodeItemToKill.value(); - emit nodeKilled(nodeItemToKill.value()); - return _nodeHash.erase(nodeItemToKill); -} - -void NodeList::processKillNode(const QByteArray& dataByteArray) { - // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader(dataByteArray), NUM_BYTES_RFC4122_UUID)); - - // kill the node with this UUID, if it exists - killNodeWithUUID(nodeUUID); -} - -void NodeList::sendDomainServerCheckIn() { - if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { - // we don't know our public socket and we need to send it to the domain server - // send a STUN request to figure it out - sendSTUNRequest(); - } else if (!_domainInfo.getIP().isNull()) { - if (_domainInfo.getRootAuthenticationURL().isEmpty() - || !_sessionUUID.isNull() - || !_domainInfo.getRegistrationToken().isEmpty() ) { - // construct the DS check in packet - - PacketType domainPacketType = _sessionUUID.isNull() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; - - QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest) - ? _sessionUUID : _domainInfo.getAssignmentUUID(); - - QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); - QDataStream packetStream(&domainServerPacket, QIODevice::Append); - - if (domainPacketType == PacketTypeDomainConnectRequest) { - // we may need a registration token to present to the domain-server - packetStream << (quint8) !_domainInfo.getRegistrationToken().isEmpty(); - - if (!_domainInfo.getRegistrationToken().isEmpty()) { - // if we have a registration token send that along in the request - packetStream << _domainInfo.getRegistrationToken(); - } - } - - // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr - << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) - << (quint8) _nodeTypesOfInterest.size(); - - // copy over the bytes for node types of interest, if required - foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { - packetStream << nodeTypeOfInterest; - } - - writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; - static unsigned int numDomainCheckins = 0; - - // send a STUN request every Nth domain server check in so we update our public socket, if required - if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { - sendSTUNRequest(); - } - - if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { - // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS - // so emit our signal that indicates that - emit limitOfSilentDomainCheckInsReached(); - } - - // increment the count of un-replied check-ins - _numNoReplyDomainCheckIns++; - } else if (AccountManager::getInstance().hasValidAccessToken()) { - // we have an access token we can use for the authentication server the domain-server requested - // so ask that server to provide us with information to connect to the domain-server - requestAuthForDomainServer(); - } - } -} - -void NodeList::setSessionUUID(const QUuid& sessionUUID) { - QUuid oldUUID = _sessionUUID; - _sessionUUID = sessionUUID; - - if (sessionUUID != oldUUID) { - qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) - << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); - emit uuidChanged(sessionUUID); - } -} - -int NodeList::processDomainServerList(const QByteArray& packet) { - // this is a packet from the domain server, reset the count of un-replied check-ins - _numNoReplyDomainCheckIns = 0; - - // if this was the first domain-server list from this domain, we've now connected - _domainInfo.setIsConnected(true); - - int readNodes = 0; - - // setup variables to read into from QDataStream - qint8 nodeType; - - QUuid nodeUUID, connectionUUID; - - HifiSockAddr nodePublicSocket; - HifiSockAddr nodeLocalSocket; - - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - // pull our owner UUID from the packet, it's always the first thing - QUuid newUUID; - packetStream >> newUUID; - setSessionUUID(newUUID); - - // pull each node in the packet - while(packetStream.device()->pos() < packet.size()) { - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; - - // if the public socket address is 0 then it's reachable at the same IP - // as the domain server - if (nodePublicSocket.getAddress().isNull()) { - nodePublicSocket.setAddress(_domainInfo.getIP()); - } - - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); - - packetStream >> connectionUUID; - node->setConnectionSecret(connectionUUID); - } - - // ping inactive nodes in conjunction with receipt of list from domain-server - // this makes it happen every second and also pings any newly added nodes - pingInactiveNodes(); - - return readNodes; -} - -void NodeList::domainServerAuthReply(const QJsonObject& jsonObject) { - _domainInfo.parseAuthInformationFromJsonObject(jsonObject); -} - -void NodeList::requestAuthForDomainServer() { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "domainServerAuthReply"; - - AccountManager::getInstance().authenticatedRequest("/api/v1/domains/" - + uuidStringWithoutCurlyBraces(_domainInfo.getUUID()) + "/auth.json", - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void NodeList::processDomainServerAuthRequest(const QByteArray& packet) { - QDataStream authPacketStream(packet); - authPacketStream.skipRawData(numBytesForPacketHeader(packet)); - - _domainInfo.setUUID(uuidFromPacketHeader(packet)); - AccountManager& accountManager = AccountManager::getInstance(); - - // grab the hostname this domain-server wants us to authenticate with - QUrl authenticationRootURL; - authPacketStream >> authenticationRootURL; - - accountManager.setAuthURL(authenticationRootURL); - _domainInfo.setRootAuthenticationURL(authenticationRootURL); - - if (AccountManager::getInstance().checkAndSignalForAccessToken()) { - // request a domain-server auth - requestAuthForDomainServer(); - } -} - -void NodeList::sendAssignment(Assignment& assignment) { - - PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand - ? PacketTypeCreateAssignment - : PacketTypeRequestAssignment; - - QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); - QDataStream packetStream(&packet, QIODevice::Append); - - packetStream << assignment; - - static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); - - const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() - ? &DEFAULT_ASSIGNMENT_SOCKET - : &_assignmentServerSocket; - - _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); -} - -QByteArray NodeList::constructPingPacket(PingType_t pingType) { - QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); - - QDataStream packetStream(&pingPacket, QIODevice::Append); - - packetStream << pingType; - packetStream << usecTimestampNow(); - - return pingPacket; -} - -QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { - QDataStream pingPacketStream(pingPacket); - pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); - - PingType_t typeFromOriginalPing; - pingPacketStream >> typeFromOriginalPing; - - quint64 timeFromOriginalPing; - pingPacketStream >> timeFromOriginalPing; - - QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); - QDataStream packetStream(&replyPacket, QIODevice::Append); - - packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); - - return replyPacket; -} - -void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { - - // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = constructPingPacket(PingType::Local); - writeDatagram(localPingPacket, node, node->getLocalSocket()); - - QByteArray publicPingPacket = constructPingPacket(PingType::Public); - writeDatagram(publicPingPacket, node, node->getPublicSocket()); - - if (!node->getSymmetricSocket().isNull()) { - QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); - writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); - } -} - -SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { - _nodeHashMutex.lock(); - - if (!_nodeHash.contains(uuid)) { - - // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); - SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); - - _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); - - _nodeHashMutex.unlock(); - - qDebug() << "Added" << *newNode; - - emit nodeAdded(newNodeSharedPointer); - - return newNodeSharedPointer; - } else { - _nodeHashMutex.unlock(); - - return updateSocketsForNode(uuid, publicSocket, localSocket); - } -} - -SharedNodePointer NodeList::updateSocketsForNode(const QUuid& uuid, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { - - SharedNodePointer matchingNode = nodeWithUUID(uuid); - - if (matchingNode) { - // perform appropriate updates to this node - QMutexLocker locker(&matchingNode->getMutex()); - - // check if we need to change this node's public or local sockets - if (publicSocket != matchingNode->getPublicSocket()) { - matchingNode->setPublicSocket(publicSocket); - qDebug() << "Public socket change for node" << *matchingNode; - } - - if (localSocket != matchingNode->getLocalSocket()) { - matchingNode->setLocalSocket(localSocket); - qDebug() << "Local socket change for node" << *matchingNode; - } - } - - return matchingNode; -} - -unsigned NodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { - unsigned n = 0; - - foreach (const SharedNodePointer& node, getNodeHash()) { - // only send to the NodeTypes we are asked to send to. - if (destinationNodeTypes.contains(node->getType())) { - writeDatagram(packet, node); - ++n; - } - } - - return n; -} - -void NodeList::pingInactiveNodes() { - foreach (const SharedNodePointer& node, getNodeHash()) { - if (!node->getActiveSocket()) { - // we don't have an active link to this node, ping it to set that up - pingPunchForInactiveNode(node); - } - } -} - -void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { - // deconstruct this ping packet to see if it is a public or local reply - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint8 pingType; - packetStream >> pingType; - - // if this is a local or public ping then we can activate a socket - // we do nothing with agnostic pings, those are simply for timing - if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { - sendingNode->activateLocalSocket(); - } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { - sendingNode->activatePublicSocket(); - } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { - sendingNode->activateSymmetricSocket(); - } -} - -SharedNodePointer NodeList::soloNodeOfType(char nodeType) { - - if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { - foreach (const SharedNodePointer& node, getNodeHash()) { - if (node->getType() == nodeType) { - return node; - } - } - } - return SharedNodePointer(); -} - -void NodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { - packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); - bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); -} - -void NodeList::resetPacketStats() { - _numCollectedPackets = 0; - _numCollectedBytes = 0; - _packetStatTimer.restart(); -} - -void NodeList::removeSilentNodes() { - - _nodeHashMutex.lock(); - - NodeHash::iterator nodeItem = _nodeHash.begin(); - - while (nodeItem != _nodeHash.end()) { - SharedNodePointer node = nodeItem.value(); - - node->getMutex().lock(); - - if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) { - // call our private method to kill this node (removes it and emits the right signal) - nodeItem = killNodeAtHashIterator(nodeItem); - } else { - // we didn't kill this node, push the iterator forwards - ++nodeItem; - } - - node->getMutex().unlock(); - } - - _nodeHashMutex.unlock(); -} - -const QString QSETTINGS_GROUP_NAME = "NodeList"; -const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; - -void NodeList::loadData(QSettings *settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); - - if (domainServerHostname.size() > 0) { - _domainInfo.setHostname(domainServerHostname); - } else { - _domainInfo.setHostname(DEFAULT_DOMAIN_HOSTNAME); - } - - settings->endGroup(); -} - -void NodeList::saveData(QSettings* settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - if (_domainInfo.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { - // the user is using a different hostname, store it - settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainInfo.getHostname())); - } else { - // the user has switched back to default, remove the current setting - settings->remove(DOMAIN_SERVER_SETTING_KEY); - } - - settings->endGroup(); -} diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index fc54c62d51..cd98fbdbd2 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -15,10 +15,6 @@ #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#endif - #ifdef __APPLE__ #include #endif @@ -26,7 +22,6 @@ #include #include "OctalCode.h" -#include "PacketHeaders.h" #include "SharedUtil.h" quint64 usecTimestamp(const timeval *time) { @@ -44,7 +39,7 @@ quint64 usecTimestampNow() { return (now.tv_sec * 1000000 + now.tv_usec) + ::usecTimestampNowAdjust; } -float randFloat () { +float randFloat() { return (rand() % 10000)/10000.f; } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index f07995dce1..f41c5b8aa2 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -30,8 +30,6 @@ #include #endif -#include "PacketHeaders.h" - const int BYTES_PER_COLOR = 3; const int BYTES_PER_FLAGS = 1; typedef unsigned char rgbColor[BYTES_PER_COLOR]; @@ -45,7 +43,6 @@ struct xColor { unsigned char blue; }; - static const float ZERO = 0.0f; static const float ONE = 1.0f; static const float ONE_HALF = 0.5f; @@ -69,8 +66,6 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; const int BITS_IN_BYTE = 8; -const int MAX_PACKET_SIZE = 1500; - quint64 usecTimestamp(const timeval *time); quint64 usecTimestampNow(); void usecTimestampNowForceClockSkew(int clockSkew); diff --git a/libraries/shared/src/Syssocket.h b/libraries/shared/src/Syssocket.h deleted file mode 100644 index 36713a2b7b..0000000000 --- a/libraries/shared/src/Syssocket.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __Syssocket__ -#define __Syssocket__ - -#ifdef _WIN32 -#define WINSOCK_API_LINKAGE -#include -#ifndef _timeval_ -#define _timeval_ -#endif -typedef SSIZE_T ssize_t; -typedef ULONG32 in_addr_t; -typedef USHORT in_port_t; -typedef USHORT uint16_t; -typedef ULONG32 socklen_t; - -#endif _Win32 - -#endif __Syssocket__ \ No newline at end of file diff --git a/libraries/shared/src/Systime.cpp b/libraries/shared/src/Systime.cpp index 7d8764e3f9..ab32821a0f 100644 --- a/libraries/shared/src/Systime.cpp +++ b/libraries/shared/src/Systime.cpp @@ -1,14 +1,61 @@ -#ifdef _WIN32 -#include -#define _timeval_ +// +// Systime.cpp +// libraries/shared/src +// +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifdef WIN32 + #include "Systime.h" - int gettimeofday( timeval* p_tv, timezone* p_tz ) - { - int tt = timeGetTime(); +/** + * gettimeofday + * Implementation according to: + * The Open Group Base Specifications Issue 6 + * IEEE Std 1003.1, 2004 Edition + */ + +/** + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Contributed by: + * Danny Smith + */ - p_tv->tv_sec = tt / 1000; - p_tv->tv_usec = tt % 1000 * 1000; - return 0; - } -#endif +#define WIN32_LEAN_AND_MEAN +#include + +/** Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */ +#define _W32_FT_OFFSET (116444736000000000ULL) + +int gettimeofday(timeval* p_tv, timezone* p_tz) { + + union { + unsigned long long ns100; /**time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if (p_tv) { + GetSystemTimeAsFileTime (&_now.ft); + p_tv->tv_usec=(long)((_now.ns100 / 10ULL) % 1000000ULL ); + p_tv->tv_sec= (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000ULL); + } + + /** Always return 0 as per Open Group Base Specifications Issue 6. + Do not set errno on error. */ + return 0; +} + +#endif \ No newline at end of file diff --git a/libraries/shared/src/Systime.h b/libraries/shared/src/Systime.h index 1d25de0b80..3098f09ecd 100644 --- a/libraries/shared/src/Systime.h +++ b/libraries/shared/src/Systime.h @@ -1,39 +1,27 @@ -#ifndef __Systime__ -#define __Systime__ +// +// Systime.h +// libraries/shared/src +// +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_Systime_h +#define hifi_Systime_h -#ifdef _WIN32 +#ifdef WIN32 -#ifdef _WINSOCK2API_ -#define _timeval_ -#endif - -#ifndef _timeval_ -#define _timeval_ -/* - * Structure returned by gettimeofday(2) system call, - * and used in other calls. - */ - -// this is a bit of a hack for now, but sometimes on windows -// we need timeval defined here, sometimes we get it -// from winsock.h -#ifdef WANT_TIMEVAL -struct timeval { - long tv_sec; /* seconds */ - long tv_usec; /* and microseconds */ -}; -#endif - -#endif _timeval_ +#include struct timezone { int tz_minuteswest; /* minutes west of Greenwich */ int tz_dsttime; /* type of dst correction */ }; -int gettimeofday( struct timeval* p_tv, struct timezone* p_tz ); +int gettimeofday(struct timeval* p_tv, struct timezone* p_tz); -#endif _Win32 +#endif -#endif __Systime__ +#endif // hifi_Systime_h \ No newline at end of file diff --git a/libraries/voxels/CMakeLists.txt b/libraries/voxels/CMakeLists.txt index afc0b34b64..bdba388594 100644 --- a/libraries/voxels/CMakeLists.txt +++ b/libraries/voxels/CMakeLists.txt @@ -24,9 +24,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets Qt5::Script) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets Qt5::Script) \ No newline at end of file diff --git a/voxel-edit/CMakeLists.txt b/voxel-edit/CMakeLists.txt index 0de8fd059e..cc0a122bf0 100644 --- a/voxel-edit/CMakeLists.txt +++ b/voxel-edit/CMakeLists.txt @@ -31,8 +31,19 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +# link in the hifi networking library +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + +# link GnuTLS +find_package(GnuTLS REQUIRED) + IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) + + # add a definition for ssize_t so that windows doesn't bail on gnutls.h + add_definitions(-Dssize_t=long) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Script) \ No newline at end of file +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file