//// 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 <math.h>#include <QtCore/QJsonDocument>#include "Assignment.h"#include "HifiSockAddr.h"#include "NodeList.h"#include "PacketHeaders.h"#include "SharedUtil.h"#include "UserActivityLogger.h"#include "NetworkLogging.h"#include "DomainHandler.h"DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _assignmentUUID(), _iceDomainID(), _iceClientID(), _iceServerSockAddr(), _icePeer(this), _isConnected(false), _settingsObject(), _failedSettingsRequests(0){ // if we get a socket that make sure our NetworkPeer ping timer stops connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer);}void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); _icePeer.reset(); if (requiresICE()) { // if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server _sockAddr.clear(); } setIsConnected(false);}void DomainHandler::clearSettings() { _settingsObject = QJsonObject(); _failedSettingsRequests = 0;}void DomainHandler::softReset() { qCDebug(networking) << "Resetting current domain connection information."; clearConnectionInfo(); clearSettings();}void DomainHandler::hardReset() { softReset(); qCDebug(networking) << "Hard reset in NodeList DomainHandler."; _iceDomainID = QUuid(); _iceServerSockAddr = HifiSockAddr(); _hostname = QString(); _sockAddr.clear(); // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear();}void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { if (_sockAddr != sockAddr) { // we should reset on a sockAddr change hardReset(); // change the sockAddr _sockAddr = sockAddr; } if (!_sockAddr.isNull()) { DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); } // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification _hostname = hostname;}void DomainHandler::setUUID(const QUuid& uuid) { if (uuid != _uuid) { _uuid = uuid; qCDebug(networking) << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid); }}void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { if (hostname != _hostname || _sockAddr.getPort() != port) { // re-set the domain info so that auth information is reloaded hardReset(); if (hostname != _hostname) { // set the new hostname _hostname = hostname; qCDebug(networking) << "Updated domain hostname to" << _hostname; // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainHostname); UserActivityLogger::getInstance().changedDomain(_hostname); emit hostnameChanged(_hostname); } if (_sockAddr.getPort() != port) { qCDebug(networking) << "Updated domain port to" << port; } // grab the port by reading the string after the colon _sockAddr.setPort(port); }}void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { if (id != _uuid) { // re-set the domain info to connect to new domain hardReset(); _iceDomainID = id; HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; replaceableSockAddr->~HifiSockAddr(); replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); auto nodeList = DependencyManager::get<NodeList>(); nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerHostname); if (_iceServerSockAddr.getAddress().isNull()) { // connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up connect(&_iceServerSockAddr, &HifiSockAddr::lookupCompleted, this, &DomainHandler::completedIceServerHostnameLookup); } else { completedIceServerHostnameLookup(); } // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname; }}void DomainHandler::activateICELocalSocket() { DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getLocalSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery();}void DomainHandler::activateICEPublicSocket() { DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getPublicSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery();}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]); DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(), _sockAddr.getAddress().toString().toLocal8Bit().constData()); emit completedSocketDiscovery(); return; } } // if we got here then we failed to lookup the address qCDebug(networking, "Failed domain server lookup");}void DomainHandler::completedIceServerHostnameLookup() { qDebug() << "ICE server socket is at" << _iceServerSockAddr; DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerSocket); // emit our signal so we can send a heartbeat to ice-server immediately emit iceSocketAndIDReceived();}void DomainHandler::setIsConnected(bool isConnected) { if (_isConnected != isConnected) { _isConnected = isConnected; if (_isConnected) { emit connectedToDomain(_hostname); // we've connected to new domain - time to ask it for global settings requestDomainSettings(); } else { emit disconnectedFromDomain(); } }}void DomainHandler::requestDomainSettings() { NodeType_t owningNodeType = DependencyManager::get<NodeList>()->getOwnerType(); if (owningNodeType == NodeType::Agent) { // for now the agent nodes don't need any settings - this allows local assignment-clients // to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet) _settingsObject = QJsonObject(); emit settingsReceived(_settingsObject); } else { if (_settingsObject.isEmpty()) { // setup the URL required to grab settings JSON QUrl settingsJSONURL; settingsJSONURL.setScheme("http"); settingsJSONURL.setHost(_hostname); settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT); settingsJSONURL.setPath("/settings.json"); Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get<NodeList>()->getOwnerType()); settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType)); qCDebug(networking) << "Requesting domain-server settings at" << settingsJSONURL.toString(); QNetworkRequest settingsRequest(settingsJSONURL); settingsRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = NetworkAccessManager::getInstance().get(settingsRequest); connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished); } }}const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;void DomainHandler::settingsRequestFinished() { QNetworkReply* settingsReply = reinterpret_cast<QNetworkReply*>(sender()); int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) { // parse the JSON to a QJsonObject and save it _settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object(); qCDebug(networking) << "Received domain settings."; emit settingsReceived(_settingsObject); // reset failed settings requests to 0, we got them _failedSettingsRequests = 0; } else { // error grabbing the settings - in some cases this means we are stuck // so we should retry until we get it qCDebug(networking) << "Error getting domain settings -" << settingsReply->errorString() << "- retrying"; if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) { qCDebug(networking) << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS << "times. Re-setting connection to domain."; clearSettings(); clearConnectionInfo(); emit settingsReceiveFail(); } else { requestDomainSettings(); } } settingsReply->deleteLater();}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)); qCDebug(networking) << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS."; _sockAddr.setPort(dtlsPort);// initializeDTLSSession();}void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { QDataStream iceResponseStream(icePacket); iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket)); iceResponseStream >> _icePeer; DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); if (_icePeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; _icePeer.reset(); } else { qCDebug(networking) << "Received network peer object for domain -" << _icePeer; // ask the peer object to start its ping timer _icePeer.startPingTimer(); // emit our signal so the NodeList knows to send a ping immediately emit icePeerSocketsReceived(); }}
//
// 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 <math.h>
#include <QtCore/QJsonDocument>
#include "Assignment.h"
#include "HifiSockAddr.h"
#include "NodeList.h"
#include "PacketHeaders.h"
#include "SharedUtil.h"
#include "UserActivityLogger.h"
#include "NetworkLogging.h"
#include "DomainHandler.h"
DomainHandler::DomainHandler(QObject* parent) :
QObject(parent),
_uuid(),
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_iceDomainID(),
_iceClientID(),
_iceServerSockAddr(),
_icePeer(this),
_isConnected(false),
_settingsObject(),
_failedSettingsRequests(0)
{
// if we get a socket that make sure our NetworkPeer ping timer stops
connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer);
}
void DomainHandler::clearConnectionInfo() {
_uuid = QUuid();
_icePeer.reset();
if (requiresICE()) {
// if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server
_sockAddr.clear();
}
setIsConnected(false);
}
void DomainHandler::clearSettings() {
_settingsObject = QJsonObject();
_failedSettingsRequests = 0;
}
void DomainHandler::softReset() {
qCDebug(networking) << "Resetting current domain connection information.";
clearConnectionInfo();
clearSettings();
}
void DomainHandler::hardReset() {
softReset();
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
_iceDomainID = QUuid();
_iceServerSockAddr = HifiSockAddr();
_hostname = QString();
_sockAddr.clear();
// clear any pending path we may have wanted to ask the previous DS about
_pendingPath.clear();
}
void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) {
if (_sockAddr != sockAddr) {
// we should reset on a sockAddr change
hardReset();
// change the sockAddr
_sockAddr = sockAddr;
}
if (!_sockAddr.isNull()) {
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
}
// some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification
_hostname = hostname;
}
void DomainHandler::setUUID(const QUuid& uuid) {
if (uuid != _uuid) {
_uuid = uuid;
qCDebug(networking) << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid);
}
}
void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
if (hostname != _hostname || _sockAddr.getPort() != port) {
// re-set the domain info so that auth information is reloaded
hardReset();
if (hostname != _hostname) {
// set the new hostname
_hostname = hostname;
qCDebug(networking) << "Updated domain hostname to" << _hostname;
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainHostname);
UserActivityLogger::getInstance().changedDomain(_hostname);
emit hostnameChanged(_hostname);
}
if (_sockAddr.getPort() != port) {
qCDebug(networking) << "Updated domain port to" << port;
}
// grab the port by reading the string after the colon
_sockAddr.setPort(port);
}
}
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
if (id != _uuid) {
// re-set the domain info to connect to new domain
hardReset();
_iceDomainID = id;
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
replaceableSockAddr->~HifiSockAddr();
replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerHostname);
if (_iceServerSockAddr.getAddress().isNull()) {
// connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up
connect(&_iceServerSockAddr, &HifiSockAddr::lookupCompleted, this, &DomainHandler::completedIceServerHostnameLookup);
} else {
completedIceServerHostnameLookup();
}
// refresh our ICE client UUID to something new
_iceClientID = QUuid::createUuid();
qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname;
}
}
void DomainHandler::activateICELocalSocket() {
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
_sockAddr = _icePeer.getLocalSocket();
_hostname = _sockAddr.getAddress().toString();
emit completedSocketDiscovery();
}
void DomainHandler::activateICEPublicSocket() {
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
_sockAddr = _icePeer.getPublicSocket();
_hostname = _sockAddr.getAddress().toString();
emit completedSocketDiscovery();
}
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]);
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(),
_sockAddr.getAddress().toString().toLocal8Bit().constData());
emit completedSocketDiscovery();
return;
}
}
// if we got here then we failed to lookup the address
qCDebug(networking, "Failed domain server lookup");
}
void DomainHandler::completedIceServerHostnameLookup() {
qDebug() << "ICE server socket is at" << _iceServerSockAddr;
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerSocket);
// emit our signal so we can send a heartbeat to ice-server immediately
emit iceSocketAndIDReceived();
}
void DomainHandler::setIsConnected(bool isConnected) {
if (_isConnected != isConnected) {
_isConnected = isConnected;
if (_isConnected) {
emit connectedToDomain(_hostname);
// we've connected to new domain - time to ask it for global settings
requestDomainSettings();
} else {
emit disconnectedFromDomain();
}
}
}
void DomainHandler::requestDomainSettings() {
NodeType_t owningNodeType = DependencyManager::get<NodeList>()->getOwnerType();
if (owningNodeType == NodeType::Agent) {
// for now the agent nodes don't need any settings - this allows local assignment-clients
// to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet)
_settingsObject = QJsonObject();
emit settingsReceived(_settingsObject);
} else {
if (_settingsObject.isEmpty()) {
// setup the URL required to grab settings JSON
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(_hostname);
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get<NodeList>()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
qCDebug(networking) << "Requesting domain-server settings at" << settingsJSONURL.toString();
QNetworkRequest settingsRequest(settingsJSONURL);
settingsRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = NetworkAccessManager::getInstance().get(settingsRequest);
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
}
}
}
const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
void DomainHandler::settingsRequestFinished() {
QNetworkReply* settingsReply = reinterpret_cast<QNetworkReply*>(sender());
int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) {
// parse the JSON to a QJsonObject and save it
_settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object();
qCDebug(networking) << "Received domain settings.";
emit settingsReceived(_settingsObject);
// reset failed settings requests to 0, we got them
_failedSettingsRequests = 0;
} else {
// error grabbing the settings - in some cases this means we are stuck
// so we should retry until we get it
qCDebug(networking) << "Error getting domain settings -" << settingsReply->errorString() << "- retrying";
if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
qCDebug(networking) << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS
<< "times. Re-setting connection to domain.";
clearSettings();
clearConnectionInfo();
emit settingsReceiveFail();
} else {
requestDomainSettings();
}
}
settingsReply->deleteLater();
}
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));
qCDebug(networking) << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS.";
_sockAddr.setPort(dtlsPort);
// initializeDTLSSession();
}
void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) {
QDataStream iceResponseStream(icePacket);
iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket));
iceResponseStream >> _icePeer;
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
if (_icePeer.getUUID() != _iceDomainID) {
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
_icePeer.reset();
} else {
qCDebug(networking) << "Received network peer object for domain -" << _icePeer;
// ask the peer object to start its ping timer
_icePeer.startPingTimer();
// emit our signal so the NodeList knows to send a ping immediately
emit icePeerSocketsReceived();
}
}