682 lines · 25.1 KB · C++
RawDownload
////  DomainHandler.cpp//  libraries/networking/src////  Created by Stephen Birarda on 2/18/2014.//  Copyright 2014 High Fidelity, Inc.//  Copyright 2020 Vircadia contributors.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "DomainHandler.h" #include <math.h> #include <PathUtils.h> #include <shared/QtHelpers.h> #include <QThread> #include <QtCore/QJsonDocument>#include <QtCore/QDataStream> #include "AddressManager.h"#include "Assignment.h"#include "DomainAccountManager.h"#include "SockAddr.h"#include "NodeList.h"#include "udt/Packet.h"#include "udt/PacketHeaders.h"#include "NLPacket.h"#include "SharedUtil.h"#include "UserActivityLogger.h"#include "NetworkLogging.h" DomainHandler::DomainHandler(QObject* parent) :    QObject(parent),    _sockAddr(SockAddr(SocketType::UDP, QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),    _icePeer(this),    _settingsTimer(this),    _apiRefreshTimer(this){    _sockAddr.setObjectName("DomainServer");     // if we get a socket that make sure our NetworkPeer ping timer stops    connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer);     // setup a timeout for failure on settings requests    static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000;    _settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); // 5s, Qt::CoarseTimer acceptable    connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail);     // setup the API refresh timer for auto connection information refresh from API when failing to connect    const int API_REFRESH_TIMEOUT_MSEC = 2500;    _apiRefreshTimer.setInterval(API_REFRESH_TIMEOUT_MSEC); // 2.5s, Qt::CoarseTimer acceptable     auto addressManager = DependencyManager::get<AddressManager>();    connect(&_apiRefreshTimer, &QTimer::timeout, addressManager.data(), &AddressManager::refreshPreviousLookup);     // stop the refresh timer if we connect to a domain    connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop);     // stop the refresh timer if redirected to the error domain    connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop);} void DomainHandler::disconnect(QString reason) {    // if we're currently connected to a domain, send a disconnect packet on our way out    if (_isConnected) {        sendDisconnectPacket();    }     // clear member variables that hold the connection state to a domain    _uuid = QUuid();    _connectionToken = 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();    }     qCDebug(networking_ice) << "Disconnecting from domain server.";    qCDebug(networking_ice) << "REASON:" << reason;    setIsConnected(false);} void DomainHandler::sendDisconnectPacket() {    // The DomainDisconnect packet is not verified - we're relying on the eventual addition of DTLS to the    // domain-server connection to stop greifing here     // construct the disconnect packet once (an empty packet but sourced with our current session UUID)    static auto disconnectPacket = NLPacket::create(PacketType::DomainDisconnectRequest, 0);     // send the disconnect packet to the current domain server    auto nodeList = DependencyManager::get<NodeList>();    nodeList->sendUnreliablePacket(*disconnectPacket, _sockAddr);} void DomainHandler::clearSettings() {    _settingsObject = QJsonObject();} void DomainHandler::softReset(QString reason) {    qCDebug(networking) << "Resetting current domain connection information.";    disconnect(reason);     clearSettings();     _connectionDenialsSinceKeypairRegen = 0;    _checkInPacketsSinceLastReply = 0;     // cancel the failure timeout for any pending requests for settings    QMetaObject::invokeMethod(&_settingsTimer, "stop");     // restart the API refresh timer in case we fail to connect and need to refresh information    if (!_isInErrorState) {        QMetaObject::invokeMethod(&_apiRefreshTimer, "start");    }} void DomainHandler::hardReset(QString reason) {    emit resetting();     softReset(reason);    _haveAskedConnectWithoutAvatarEntities = false;    _canConnectWithoutAvatarEntities = false;    _isInErrorState = false;    emit redirectErrorStateChanged(_isInErrorState);     qCDebug(networking) << "Hard reset in NodeList DomainHandler.";    _pendingDomainID = QUuid();    _iceServerSockAddr = SockAddr();    _sockAddr.clear();    _domainURL = QUrl();     _domainConnectionRefusals.clear();     _hasCheckedForAccessToken = false;     // clear any pending path we may have wanted to ask the previous DS about    _pendingPath.clear();} bool DomainHandler::isHardRefusal(int reasonCode) {    return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch ||            reasonCode == (int)ConnectionRefusedReason::TooManyUsers ||            reasonCode == (int)ConnectionRefusedReason::NotAuthorizedMetaverse ||            reasonCode == (int)ConnectionRefusedReason::NotAuthorizedDomain ||            reasonCode == (int)ConnectionRefusedReason::TimedOut);} bool DomainHandler::getInterstitialModeEnabled() const {    return _interstitialModeSettingLock.resultWithReadLock<bool>([&] {        return _enableInterstitialMode.get();    });} void DomainHandler::setInterstitialModeEnabled(bool enableInterstitialMode) {    _interstitialModeSettingLock.withWriteLock([&] {        _enableInterstitialMode.set(enableInterstitialMode);    });} void DomainHandler::setErrorDomainURL(const QUrl& url) {    _errorDomainURL = url;    return;} void DomainHandler::setSockAddr(const SockAddr& sockAddr, const QString& hostname) {    if (_sockAddr != sockAddr) {        // we should reset on a sockAddr change        hardReset("Changing domain sockAddr");        // 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    _domainURL = QUrl();    _domainURL.setScheme(URL_SCHEME_OVERTE);    _domainURL.setHost(hostname);    _domainURL.setPort(_sockAddr.getPort());} void DomainHandler::setUUID(const QUuid& uuid) {    if (uuid != _uuid) {        _uuid = uuid;        qCDebug(networking) << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid);    }} void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {    _pendingDomainID = domainID;     if (domainURL.scheme() != URL_SCHEME_OVERTE) {        _sockAddr.clear();         // if this is a file URL we need to see if it has a ~ for us to expand        if (domainURL.scheme() == HIFI_URL_SCHEME_FILE) {            domainURL = PathUtils::expandToLocalDataAbsolutePath(domainURL);        }    }     auto domainPort = domainURL.port();    if (domainPort == -1) {        domainPort = DEFAULT_DOMAIN_SERVER_PORT;    }     // if it's in the error state, reset and try again.    if (_domainURL != domainURL         || (_sockAddr.getPort() != domainPort && domainURL.scheme() == URL_SCHEME_OVERTE)        || isServerless() // For reloading content in serverless domain.        || _isInErrorState) {        // re-set the domain info so that auth information is reloaded        hardReset("Changing domain URL");         QString previousHost = _domainURL.host();        _domainURL = domainURL;         _hasCheckedForDomainAccessToken = false;         if (previousHost != domainURL.host()) {            qCDebug(networking) << "Updated domain hostname to" << domainURL.host();             if (!domainURL.host().isEmpty()) {                if (domainURL.scheme() == URL_SCHEME_OVERTE) {                    // 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.", domainURL.host().toLocal8Bit().constData());                    QHostInfo::lookupHost(domainURL.host(), this, SLOT(completedHostnameLookup(const QHostInfo&)));                }                 DependencyManager::get<NodeList>()->flagTimeForConnectionStep(                    LimitedNodeList::ConnectionStep::SetDomainHostname);                 UserActivityLogger::getInstance().changedDomain(domainURL.host());            }        }         DependencyManager::get<DomainAccountManager>()->setDomainURL(_domainURL);         emit domainURLChanged(_domainURL);         if (_sockAddr.getPort() != domainPort) {            qCDebug(networking) << "Updated domain port to" << domainPort;            _sockAddr.setPort(domainPort);        }    }} void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {     auto newIceServer = _iceServerSockAddr.getAddress().toString() != iceServerHostname;    auto newDomainID = id != _pendingDomainID;     // if it's in the error state, reset and try again.    if (newIceServer || newDomainID || _isInErrorState) {        QString reason;        if (newIceServer) {            reason += "New ICE server;";        }        if (newDomainID) {            reason += "New domain ID;";        }        if (_isInErrorState) {            reason += "Domain in error state;";        }         // re-set the domain info to connect to new domain        hardReset(reason);         // refresh our ICE client UUID to something new        _iceClientID = QUuid::createUuid();         _pendingDomainID = id;         SockAddr* replaceableSockAddr = &_iceServerSockAddr;        replaceableSockAddr->~SockAddr();        replaceableSockAddr = new (replaceableSockAddr) SockAddr(SocketType::UDP, iceServerHostname, ICE_SERVER_DEFAULT_PORT);        _iceServerSockAddr.setObjectName("IceServer");         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, &SockAddr::lookupCompleted, this, &DomainHandler::completedIceServerHostnameLookup);        } else {            completedIceServerHostnameLookup();        }         qCDebug(networking_ice) << "ICE required to connect to domain via ice server at" << iceServerHostname;    }} void DomainHandler::activateICELocalSocket() {    DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);    _sockAddr = _icePeer.getLocalSocket();    _domainURL.setScheme(URL_SCHEME_OVERTE);    _domainURL.setHost(_sockAddr.getAddress().toString());    emit domainURLChanged(_domainURL);    emit completedSocketDiscovery();} void DomainHandler::activateICEPublicSocket() {    DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);    _sockAddr = _icePeer.getPublicSocket();    _domainURL.setScheme(URL_SCHEME_OVERTE);    _domainURL.setHost(_sockAddr.getAddress().toString());    emit domainURLChanged(_domainURL);    emit completedSocketDiscovery();} QString DomainHandler::getViewPointFromNamedPath(QString namedPath) {    auto lookup = _namedPaths.find(namedPath);    if (lookup != _namedPaths.end()) {        return lookup->second;    }    if (namedPath == DEFAULT_NAMED_PATH) {        return DOMAIN_SPAWNING_POINT;    }    return "";} 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", _domainURL.host().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() {    qCDebug(networking_ice) << "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) {            _lastDomainConnectionError = -1;            emit connectedToDomain(_domainURL);             // FIXME: Reinstate the requestDomainSettings() call here in version 2021.2.0 instead of having it in             // NodeList::processDomainList().            /*            if (_domainURL.scheme() == URL_SCHEME_OVERTE && !_domainURL.host().isEmpty()) {                // we've connected to new domain - time to ask it for global settings                requestDomainSettings();            }            */         } else {            emit disconnectedFromDomain();        }    }} void DomainHandler::setCanConnectWithoutAvatarEntities(bool canConnect) {    _canConnectWithoutAvatarEntities = canConnect;    _haveAskedConnectWithoutAvatarEntities = true;} bool DomainHandler::canConnectWithoutAvatarEntities() {    if (!_canConnectWithoutAvatarEntities && !_haveAskedConnectWithoutAvatarEntities) {        if (_isConnected) {            // Already connected so don't ask. (Permission removed from user while in the domain.)            setCanConnectWithoutAvatarEntities(true);        } else {            // Ask whether to connect to the domain.            emit confirmConnectWithoutAvatarEntities();        }    }    return _canConnectWithoutAvatarEntities;} void DomainHandler::connectedToServerless(std::map<QString, QString> namedPaths) {    _namedPaths = namedPaths;    setIsConnected(true);} void DomainHandler::loadedErrorDomain(std::map<QString, QString> namedPaths) {    auto lookup = namedPaths.find("/");    QString viewpoint;    if (lookup != namedPaths.end()) {        viewpoint = lookup->second;    } else {        viewpoint = DOMAIN_SPAWNING_POINT;    }    DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, QString());} void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) {    _lastDomainConnectionError = reasonCode;    if (getInterstitialModeEnabled() && isHardRefusal(reasonCode)) {        _errorDomainURL = errorUrl;        _isInErrorState = true;        qCDebug(networking) << "Error connecting to domain: " << reasonMessage;        emit redirectErrorStateChanged(_isInErrorState);        emit redirectToErrorDomainURL(_errorDomainURL);    } else {        emit domainConnectionRefused(reasonMessage, reasonCode, extraInfo);    }} void DomainHandler::requestDomainSettings() {    qCDebug(networking) << "Requesting settings from domain server";     Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get<NodeList>()->getOwnerType());     auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false);    packet->writePrimitive(assignmentType);     auto nodeList = DependencyManager::get<LimitedNodeList>();    nodeList->sendPacket(std::move(packet), _sockAddr);     _settingsTimer.start();} void DomainHandler::processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList) {    // stop our settings timer since we successfully requested the settings we need    _settingsTimer.stop();        auto data = packetList->getMessage();     _settingsObject = QJsonDocument::fromJson(data).object();        if (!_settingsObject.isEmpty()) {        qCDebug(networking) << "Received domain settings: \n" << _settingsObject;    }     emit settingsReceived(_settingsObject);} void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {    const SockAddr& senderSockAddr = message->getSenderSockAddr();    qCDebug(networking_ice) << "Received reply from domain-server on" << senderSockAddr;     if (getIP().isNull()) {        // we're hearing back from this domain-server, no need to refresh API information        _apiRefreshTimer.stop();         // for now we're unsafely assuming this came back from the domain        if (senderSockAddr == _icePeer.getLocalSocket()) {            qCDebug(networking_ice) << "Connecting to domain using local socket";            activateICELocalSocket();        } else if (senderSockAddr == _icePeer.getPublicSocket()) {            qCDebug(networking_ice) << "Conecting to domain using public socket";            activateICEPublicSocket();        } else {            qCDebug(networking_ice) << "Reply does not match either local or public socket for domain. Will not connect.";        }    }} void DomainHandler::processDTLSRequirementPacket(QSharedPointer<ReceivedMessage> message) {    // figure out the port that the DS wants us to use for us to talk to them with DTLS    unsigned short dtlsPort;    message->readPrimitive(&dtlsPort);     qCDebug(networking) << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS.";     _sockAddr.setPort(dtlsPort); //    initializeDTLSSession();} void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) {    if (_icePeer.hasSockets()) {        qCDebug(networking_ice) << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";        // bail on processing this packet if our ice peer already has sockets        return;    }     // start or restart the API refresh timer now that we have new information

Lines 1–500 of 682.