Merge pull request #3427 from birarda/master

add ability for domain server to update IP address in data server
This commit is contained in:
Philip Rosedale 2014-09-16 13:32:14 -07:00
commit 55611bdd55
6 changed files with 289 additions and 171 deletions

View file

@ -72,6 +72,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setupNodeListAndAssignments(); setupNodeListAndAssignments();
loadExistingSessionsFromSettings(); loadExistingSessionsFromSettings();
// check if we have the flag that enables dynamic IP
setupDynamicIPAddressUpdating();
} }
} }
@ -162,6 +165,8 @@ bool DomainServer::optionallySetupOAuth() {
return true; return true;
} }
const QString DOMAIN_CONFIG_ID_KEY = "id";
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
const QString CUSTOM_PORT_OPTION = "port"; const QString CUSTOM_PORT_OPTION = "port";
@ -190,8 +195,6 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort);
const QString DOMAIN_CONFIG_ID_KEY = "id";
// set our LimitedNodeList UUID to match the UUID from our config // 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 // 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()); nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString());
@ -209,27 +212,29 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
addStaticAssignmentsToQueue(); addStaticAssignmentsToQueue();
} }
bool DomainServer::optionallySetupAssignmentPayment() { const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
// check if we have a username and password set via env const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
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()) {
bool DomainServer::hasOAuthProviderAndAuthInformation() {
if (!_oauthProviderURL.isEmpty()) {
static bool hasAttemptedAuthWithOAuthProvider = false;
if (!hasAttemptedAuthWithOAuthProvider) {
AccountManager& accountManager = AccountManager::getInstance(); AccountManager& accountManager = AccountManager::getInstance();
accountManager.setAuthURL(_oauthProviderURL); accountManager.setAuthURL(_oauthProviderURL);
if (!accountManager.hasValidAccessToken()) { if (!accountManager.hasValidAccessToken()) {
// we don't have a valid access token so we need to get one // 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 username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY); QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
if (!username.isEmpty() && !password.isEmpty()) { if (!username.isEmpty() && !password.isEmpty()) {
accountManager.requestAccessToken(username, password); accountManager.requestAccessToken(username, password);
// connect to loginFailed signal from AccountManager so we can quit if that is the case // connect to loginFailed signal from AccountManager so we can quit if that is the case
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed); connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
} else { } else {
@ -238,34 +243,85 @@ bool DomainServer::optionallySetupAssignmentPayment() {
return false; return false;
} }
} }
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString()); hasAttemptedAuthWithOAuthProvider = true;
// 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;
} }
return 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; 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()) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const QUuid& domainID = nodeList->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);
// 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();
} 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() { void DomainServer::loginFailed() {
qDebug() << "Login to data server has failed. domain-server will now quit"; qDebug() << "Login to data server has failed. domain-server will now quit";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
@ -836,37 +892,68 @@ 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) { void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance(); LimitedNodeList* nodeList = LimitedNodeList::getInstance();
if (nodeList->packetVersionAndHashMatch(receivedPacket)) { if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType requestType = packetTypeForPacket(receivedPacket); PacketType requestType = packetTypeForPacket(receivedPacket);
switch (requestType) {
if (requestType == PacketTypeDomainConnectRequest) { case PacketTypeDomainConnectRequest:
handleConnectRequest(receivedPacket, senderSockAddr); handleConnectRequest(receivedPacket, senderSockAddr);
} else if (requestType == PacketTypeDomainListRequest) { break;
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); case PacketTypeDomainListRequest: {
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
NodeType_t throwawayNodeType; if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
HifiSockAddr nodePublicAddress, nodeLocalAddress; NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress;
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr); int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
// update last receive to now
quint64 timeNow = usecTimestampNow(); // update last receive to now
checkInNode->setLastHeardMicrostamp(timeNow); quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
}
break;
} }
} else if (requestType == PacketTypeNodeJsonStats) { case PacketTypeNodeJsonStats: {
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
if (matchingNode) { if (matchingNode) {
reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket);
}
break;
} }
case PacketTypeStunResponse:
nodeList->processSTUNResponse(receivedPacket);
break;
default:
break;
} }
} }
} }

View file

@ -58,11 +58,16 @@ private slots:
void readAvailableDatagrams(); void readAvailableDatagrams();
void setupPendingAssignmentCredits(); void setupPendingAssignmentCredits();
void sendPendingTransactionsToServer(); void sendPendingTransactionsToServer();
void requestCurrentIPAddressViaSTUN();
void sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr);
private: private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth(); bool optionallySetupOAuth();
bool optionallyReadX509KeyAndCertificate(); bool optionallyReadX509KeyAndCertificate();
bool hasOAuthProviderAndAuthInformation();
bool optionallySetupAssignmentPayment(); bool optionallySetupAssignmentPayment();
void setupDynamicIPAddressUpdating();
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);

View file

@ -67,6 +67,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
_nodeHashMutex(QMutex::Recursive), _nodeHashMutex(QMutex::Recursive),
_nodeSocket(this), _nodeSocket(this),
_dtlsSocket(NULL), _dtlsSocket(NULL),
_publicSockAddr(),
_numCollectedPackets(0), _numCollectedPackets(0),
_numCollectedBytes(0), _numCollectedBytes(0),
_packetStatTimer() _packetStatTimer()
@ -502,3 +503,115 @@ void LimitedNodeList::removeSilentNodes() {
_nodeHashMutex.unlock(); _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;
}

View file

@ -40,6 +40,9 @@ extern const QUrl DEFAULT_NODE_AUTH_URL;
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io";
const unsigned short STUN_SERVER_PORT = 3478;
class HifiSockAddr; class HifiSockAddr;
typedef QSet<NodeType_t> NodeSet; typedef QSet<NodeType_t> NodeSet;
@ -99,6 +102,9 @@ public:
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
void resetPacketStats(); void resetPacketStats();
virtual void sendSTUNRequest();
virtual bool processSTUNResponse(const QByteArray& packet);
public slots: public slots:
void reset(); void reset();
void eraseAllNodes(); void eraseAllNodes();
@ -110,6 +116,7 @@ signals:
void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID);
void nodeAdded(SharedNodePointer); void nodeAdded(SharedNodePointer);
void nodeKilled(SharedNodePointer); void nodeKilled(SharedNodePointer);
void publicSockAddrChanged(const HifiSockAddr& publicSockAddr);
protected: protected:
static LimitedNodeList* _sharedInstance; static LimitedNodeList* _sharedInstance;
@ -130,6 +137,7 @@ protected:
QMutex _nodeHashMutex; QMutex _nodeHashMutex;
QUdpSocket _nodeSocket; QUdpSocket _nodeSocket;
QUdpSocket* _dtlsSocket; QUdpSocket* _dtlsSocket;
HifiSockAddr _publicSockAddr;
int _numCollectedPackets; int _numCollectedPackets;
int _numCollectedBytes; int _numCollectedBytes;
QElapsedTimer _packetStatTimer; QElapsedTimer _packetStatTimer;

View file

@ -57,7 +57,6 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
_domainHandler(this), _domainHandler(this),
_numNoReplyDomainCheckIns(0), _numNoReplyDomainCheckIns(0),
_assignmentServerSocket(), _assignmentServerSocket(),
_publicSockAddr(),
_hasCompletedInitialSTUNFailure(false), _hasCompletedInitialSTUNFailure(false),
_stunRequestsSinceSuccess(0) _stunRequestsSinceSuccess(0)
{ {
@ -195,47 +194,16 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
_nodeTypesOfInterest.unite(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; const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5;
void NodeList::sendSTUNRequest() { 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) { 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), LimitedNodeList::sendSTUNRequest();
stunSockAddr.getAddress(), stunSockAddr.getPort());
_stunRequestsSinceSuccess++; _stunRequestsSinceSuccess++;
@ -255,79 +223,16 @@ void NodeList::sendSTUNRequest() {
} }
} }
void NodeList::processSTUNResponse(const QByteArray& packet) { bool NodeList::processSTUNResponse(const QByteArray& packet) {
// check the cookie to make sure this is actually a STUN response if (LimitedNodeList::processSTUNResponse(packet)) {
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS // reset the number of failed STUN requests since last success
const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4; _stunRequestsSinceSuccess = 0;
const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020);
_hasCompletedInitialSTUNFailure = true;
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
return true;
int attributeStartIndex = NUM_BYTES_STUN_HEADER; } else {
return false;
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;
}
}
} }
} }

View file

@ -89,8 +89,9 @@ private:
NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort); NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort);
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton 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 operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
void sendSTUNRequest(); void sendSTUNRequest();
void processSTUNResponse(const QByteArray& packet); bool processSTUNResponse(const QByteArray& packet);
void processDomainServerAuthRequest(const QByteArray& packet); void processDomainServerAuthRequest(const QByteArray& packet);
void requestAuthForDomainServer(); void requestAuthForDomainServer();
@ -102,7 +103,6 @@ private:
DomainHandler _domainHandler; DomainHandler _domainHandler;
int _numNoReplyDomainCheckIns; int _numNoReplyDomainCheckIns;
HifiSockAddr _assignmentServerSocket; HifiSockAddr _assignmentServerSocket;
HifiSockAddr _publicSockAddr;
bool _hasCompletedInitialSTUNFailure; bool _hasCompletedInitialSTUNFailure;
unsigned int _stunRequestsSinceSuccess; unsigned int _stunRequestsSinceSuccess;
}; };