From 47137c72a537622b43eab8689ddbc14896fe9a6a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 16 Sep 2014 12:09:07 -0700 Subject: [PATCH 1/2] groundwork for repeated dynamic IP address updating in domain-server --- domain-server/src/DomainServer.cpp | 126 ++++++++++++++++++++--------- domain-server/src/DomainServer.h | 3 + 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d17caf24f0..8ebbf9638d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -72,6 +72,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : setupNodeListAndAssignments(); loadExistingSessionsFromSettings(); + + // check if we have the flag that enables dynamic IP + setupDynamicIPAddressUpdating(); } } @@ -162,6 +165,8 @@ bool DomainServer::optionallySetupOAuth() { return true; } +const QString DOMAIN_CONFIG_ID_KEY = "id"; + void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { const QString CUSTOM_PORT_OPTION = "port"; @@ -190,8 +195,6 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); - const QString DOMAIN_CONFIG_ID_KEY = "id"; - // set our LimitedNodeList UUID to match the UUID from our config // nodes will currently use this to add resources to data-web that relate to our domain nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString()); @@ -209,27 +212,29 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -bool DomainServer::optionallySetupAssignmentPayment() { - // check if we have a username and password set via env - const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments"; - const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; - const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; - - if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) && - _argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool()) { - if (!_oauthProviderURL.isEmpty()) { +const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; +const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; +bool DomainServer::hasOAuthProviderAndAuthInformation() { + + if (!_oauthProviderURL.isEmpty()) { + + static bool hasAttemptedAuthWithOAuthProvider = false; + + if (!hasAttemptedAuthWithOAuthProvider) { AccountManager& accountManager = AccountManager::getInstance(); accountManager.setAuthURL(_oauthProviderURL); - + if (!accountManager.hasValidAccessToken()) { // we don't have a valid access token so we need to get one + // check if we have a username and password set via env QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY); QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY); - + if (!username.isEmpty() && !password.isEmpty()) { + accountManager.requestAccessToken(username, password); - + // connect to loginFailed signal from AccountManager so we can quit if that is the case connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed); } else { @@ -238,34 +243,79 @@ bool DomainServer::optionallySetupAssignmentPayment() { return false; } } - - qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); - - // assume that the fact we are authing against HF data server means we will pay for assignments - // setup a timer to send transactions to pay assigned nodes every 30 seconds - QTimer* creditSetupTimer = new QTimer(this); - connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - - const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; - creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); - - QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); - - const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; - nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); - - } else { - qDebug() << "Missing OAuth provider URL, but assigned node payment was enabled. domain-server will now quit."; - QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); - - return false; + + hasAttemptedAuthWithOAuthProvider = true; } + + } else { + qDebug() << "Missing OAuth provider URL, but a domain-server feature was required that requires authentication." << + "domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + + return false; + } +} + +bool DomainServer::optionallySetupAssignmentPayment() { + const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments"; + + if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) && + _argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() && + hasOAuthProviderAndAuthInformation()) { + + qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); + + // assume that the fact we are authing against HF data server means we will pay for assignments + // setup a timer to send transactions to pay assigned nodes every 30 seconds + QTimer* creditSetupTimer = new QTimer(this); + connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); + + const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; + creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); + + QTimer* nodePaymentTimer = new QTimer(this); + connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); + + const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; + nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); } return true; } +void DomainServer::setupDynamicIPAddressUpdating() { + const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip"; + + if (_argumentVariantMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) && + _argumentVariantMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() && + hasOAuthProviderAndAuthInformation()) { + + const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); + + if (!domainID.isNull()) { + qDebug() << "domain-server IP address will be updated for domain with ID" + << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); + + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + + // setup our timer to check our IP via stun every 30 seconds + QTimer* dynamicIPTimer = new QTimer(this); + connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentIPAddressViaSTUN); + dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); + + // check our IP address right away + requestCurrentIPAddressViaSTUN(); + + } else { + qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID." + << "Please add an id to your config.json or pass it with the command line argument --id."; + qDebug() << "Failed dynamic IP address update setup. domain-server will now quit."; + + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + } + } +} + void DomainServer::loginFailed() { qDebug() << "Login to data server has failed. domain-server will now quit"; QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); @@ -836,6 +886,10 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) { } } +void DomainServer::requestCurrentIPAddressViaSTUN() { + +} + void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 10633cc71a..101b223e60 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -58,11 +58,14 @@ private slots: void readAvailableDatagrams(); void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); + void requestCurrentIPAddressViaSTUN(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); + bool hasOAuthProviderAndAuthInformation(); bool optionallySetupAssignmentPayment(); + void setupDynamicIPAddressUpdating(); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); From 885030a10c0912c08c85af5ea046f1428e7373f9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 16 Sep 2014 12:37:22 -0700 Subject: [PATCH 2/2] handle receipt of new public socket and propogation to data-server --- domain-server/src/DomainServer.cpp | 83 +++++++++---- domain-server/src/DomainServer.h | 2 + libraries/networking/src/LimitedNodeList.cpp | 113 +++++++++++++++++ libraries/networking/src/LimitedNodeList.h | 8 ++ libraries/networking/src/NodeList.cpp | 123 +++---------------- libraries/networking/src/NodeList.h | 4 +- 6 files changed, 197 insertions(+), 136 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8ebbf9638d..e99e608906 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -246,6 +246,8 @@ bool DomainServer::hasOAuthProviderAndAuthInformation() { hasAttemptedAuthWithOAuthProvider = true; } + + return true; } else { qDebug() << "Missing OAuth provider URL, but a domain-server feature was required that requires authentication." << @@ -290,7 +292,8 @@ void DomainServer::setupDynamicIPAddressUpdating() { _argumentVariantMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() && hasOAuthProviderAndAuthInformation()) { - const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + const QUuid& domainID = nodeList->getSessionUUID(); if (!domainID.isNull()) { qDebug() << "domain-server IP address will be updated for domain with ID" @@ -303,6 +306,9 @@ void DomainServer::setupDynamicIPAddressUpdating() { connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentIPAddressViaSTUN); dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); + // send public socket changes to the data server so nodes can find us at our new IP + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer); + // check our IP address right away requestCurrentIPAddressViaSTUN(); @@ -887,7 +893,22 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) { } void DomainServer::requestCurrentIPAddressViaSTUN() { + LimitedNodeList::getInstance()->sendSTUNRequest(); +} + +void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr) { + const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; + const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); + // setup the domain object to send to the data server + const QString DOMAIN_JSON_OBJECT = "{\"domain\":{\"network_address\":\"%1\"}}"; + + QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString()); + + AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), + domainUpdateJSON.toUtf8()); } void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { @@ -895,32 +916,44 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS if (nodeList->packetVersionAndHashMatch(receivedPacket)) { PacketType requestType = packetTypeForPacket(receivedPacket); - - if (requestType == PacketTypeDomainConnectRequest) { - handleConnectRequest(receivedPacket, senderSockAddr); - } else if (requestType == PacketTypeDomainListRequest) { - QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); - - if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) { - NodeType_t throwawayNodeType; - HifiSockAddr nodePublicAddress, nodeLocalAddress; - - int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress, - 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)); + switch (requestType) { + case PacketTypeDomainConnectRequest: + handleConnectRequest(receivedPacket, senderSockAddr); + break; + case PacketTypeDomainListRequest: { + QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + + if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) { + NodeType_t throwawayNodeType; + HifiSockAddr nodePublicAddress, nodeLocalAddress; + + int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress, + 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)); + } + + break; } - } else if (requestType == PacketTypeNodeJsonStats) { - SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); - if (matchingNode) { - reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); + case PacketTypeNodeJsonStats: { + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (matchingNode) { + reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); + } + + break; } + case PacketTypeStunResponse: + nodeList->processSTUNResponse(receivedPacket); + break; + default: + break; } } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 101b223e60..f2b1b85e09 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -58,7 +58,9 @@ private slots: void readAvailableDatagrams(); void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); + void requestCurrentIPAddressViaSTUN(); + void sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index f50f7493fb..d42cab6210 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -67,6 +67,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _nodeHashMutex(QMutex::Recursive), _nodeSocket(this), _dtlsSocket(NULL), + _publicSockAddr(), _numCollectedPackets(0), _numCollectedBytes(0), _packetStatTimer() @@ -502,3 +503,115 @@ void LimitedNodeList::removeSilentNodes() { _nodeHashMutex.unlock(); } + +const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; +const int NUM_BYTES_STUN_HEADER = 20; + +void LimitedNodeList::sendSTUNRequest() { + + 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); + + _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), + stunSockAddr.getAddress(), stunSockAddr.getPort()); +} + +bool LimitedNodeList::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; + + + + int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; + + uint8_t addressFamily = 0; + memcpy(&addressFamily, packet.data() + byteIndex, 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()); + + emit publicSockAddrChanged(_publicSockAddr); + } + + return true; + } + } 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; + } + } + } + + return false; +} diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 114d3de910..337d66616e 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -40,6 +40,9 @@ extern const QUrl DEFAULT_NODE_AUTH_URL; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; +const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; +const unsigned short STUN_SERVER_PORT = 3478; + class HifiSockAddr; typedef QSet NodeSet; @@ -99,6 +102,9 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); + + virtual void sendSTUNRequest(); + virtual bool processSTUNResponse(const QByteArray& packet); public slots: void reset(); void eraseAllNodes(); @@ -110,6 +116,7 @@ signals: void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); + void publicSockAddrChanged(const HifiSockAddr& publicSockAddr); protected: static LimitedNodeList* _sharedInstance; @@ -130,6 +137,7 @@ protected: QMutex _nodeHashMutex; QUdpSocket _nodeSocket; QUdpSocket* _dtlsSocket; + HifiSockAddr _publicSockAddr; int _numCollectedPackets; int _numCollectedBytes; QElapsedTimer _packetStatTimer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 79a399c621..7ae9f9ba00 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -57,7 +57,6 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _domainHandler(this), _numNoReplyDomainCheckIns(0), _assignmentServerSocket(), - _publicSockAddr(), _hasCompletedInitialSTUNFailure(false), _stunRequestsSinceSuccess(0) { @@ -195,47 +194,16 @@ 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()); + qDebug() << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; } - - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - stunSockAddr.getAddress(), stunSockAddr.getPort()); + + LimitedNodeList::sendSTUNRequest(); _stunRequestsSinceSuccess++; @@ -255,79 +223,16 @@ void NodeList::sendSTUNRequest() { } } -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() + byteIndex, 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; - } - } +bool NodeList::processSTUNResponse(const QByteArray& packet) { + if (LimitedNodeList::processSTUNResponse(packet)) { + // reset the number of failed STUN requests since last success + _stunRequestsSinceSuccess = 0; + + _hasCompletedInitialSTUNFailure = true; + + return true; + } else { + return false; } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index af0bfeb368..d17e56fd48 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -89,8 +89,9 @@ private: 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); + bool processSTUNResponse(const QByteArray& packet); void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); @@ -102,7 +103,6 @@ private: DomainHandler _domainHandler; int _numNoReplyDomainCheckIns; HifiSockAddr _assignmentServerSocket; - HifiSockAddr _publicSockAddr; bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; };