From 47137c72a537622b43eab8689ddbc14896fe9a6a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 16 Sep 2014 12:09:07 -0700 Subject: [PATCH 1/3] 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/3] 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; }; From 9cbc53abc30ea3cd7ed1329ba890a4896113bfda Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 16 Sep 2014 13:06:46 -0700 Subject: [PATCH 3/3] pack position and orientation in silent frames --- .../src/audio/AvatarAudioStream.cpp | 46 +++++++++++-------- interface/src/Audio.cpp | 9 ++++ libraries/audio/src/InboundAudioStream.cpp | 10 +--- libraries/networking/src/PacketHeaders.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 5 ++ 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/assignment-client/src/audio/AvatarAudioStream.cpp b/assignment-client/src/audio/AvatarAudioStream.cpp index c7534d0551..90dcefa09d 100644 --- a/assignment-client/src/audio/AvatarAudioStream.cpp +++ b/assignment-client/src/audio/AvatarAudioStream.cpp @@ -19,28 +19,38 @@ AvatarAudioStream::AvatarAudioStream(bool isStereo, const InboundAudioStream::Se } int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { - - _shouldLoopbackForNode = (type == PacketTypeMicrophoneAudioWithEcho); - int readBytes = 0; - // read the channel flag - quint8 channelFlag = packetAfterSeqNum.at(readBytes); - bool isStereo = channelFlag == 1; - readBytes += sizeof(quint8); + if (type == PacketTypeSilentAudioFrame) { + const char* dataAt = packetAfterSeqNum.constData(); + quint16 numSilentSamples = *(reinterpret_cast(dataAt)); + readBytes += sizeof(quint16); + numAudioSamples = (int)numSilentSamples; - // if isStereo value has changed, restart the ring buffer with new frame size - if (isStereo != _isStereo) { - _ringBuffer.resizeForFrameSize(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - _isStereo = isStereo; + // read the positional data + readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes)); + + } else { + _shouldLoopbackForNode = (type == PacketTypeMicrophoneAudioWithEcho); + + // read the channel flag + quint8 channelFlag = packetAfterSeqNum.at(readBytes); + bool isStereo = channelFlag == 1; + readBytes += sizeof(quint8); + + // if isStereo value has changed, restart the ring buffer with new frame size + if (isStereo != _isStereo) { + _ringBuffer.resizeForFrameSize(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); + _isStereo = isStereo; + } + + // read the positional data + readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes)); + + // calculate how many samples are in this packet + int numAudioBytes = packetAfterSeqNum.size() - readBytes; + numAudioSamples = numAudioBytes / sizeof(int16_t); } - - // read the positional data - readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes)); - - // calculate how many samples are in this packet - int numAudioBytes = packetAfterSeqNum.size() - readBytes; - numAudioSamples = numAudioBytes / sizeof(int16_t); return readBytes; } diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 71b64d7c6e..79704eebf1 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -753,6 +753,15 @@ void Audio::handleAudioInput() { quint16 numSilentSamples = numNetworkSamples; memcpy(currentPacketPtr, &numSilentSamples, sizeof(quint16)); currentPacketPtr += sizeof(quint16); + + // memcpy the three float positions + memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); + currentPacketPtr += (sizeof(headPosition)); + + // memcpy our orientation + memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); + currentPacketPtr += sizeof(headOrientation); + } else { // set the mono/stereo byte *currentPacketPtr++ = isStereo; diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index cc474ea491..dda57d87da 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -109,14 +109,8 @@ int InboundAudioStream::parseData(const QByteArray& packet) { int networkSamples; - if (packetType == PacketTypeSilentAudioFrame) { - quint16 numSilentSamples = *(reinterpret_cast(dataAt)); - readBytes += sizeof(quint16); - networkSamples = (int)numSilentSamples; - } else { - // parse the info after the seq number and before the audio data (the stream properties) - readBytes += parseStreamProperties(packetType, packet.mid(readBytes), networkSamples); - } + // parse the info after the seq number and before the audio data (the stream properties) + readBytes += parseStreamProperties(packetType, packet.mid(readBytes), networkSamples); // handle this packet based on its arrival status. switch (arrivalInfo._status) { diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 5513c09a61..f04e398b98 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -51,7 +51,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeMicrophoneAudioWithEcho: return 2; case PacketTypeSilentAudioFrame: - return 3; + return 4; case PacketTypeMixedAudio: return 1; case PacketTypeAvatarData: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d85d201d88..0f285a0df3 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -505,6 +505,11 @@ void ScriptEngine::run() { // write the number of silent samples so the audio-mixer can uphold timing packetStream.writeRawData(reinterpret_cast(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t)); + // use the orientation and position of this avatar for the source of this audio + packetStream.writeRawData(reinterpret_cast(&_avatarData->getPosition()), sizeof(glm::vec3)); + glm::quat headOrientation = _avatarData->getHeadOrientation(); + packetStream.writeRawData(reinterpret_cast(&headOrientation), sizeof(glm::quat)); + } else if (nextSoundOutput) { // assume scripted avatar audio is mono and set channel flag to zero packetStream << (quint8)0;