3817 lines · 169.6 KB · C++
RawDownload
////  DomainServer.cpp//  domain-server/src////  Created by Stephen Birarda on 9/26/13.//  Copyright 2013 High Fidelity, Inc.//  Copyright 2020 Vircadia contributors.//  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "DomainServer.h" #include <memory>#include <random>#include <iostream>#include <chrono> #include <QDir>#include <QJsonDocument>#include <QJsonObject>#include <QJsonArray>#include <QProcess>#include <QSharedMemory>#include <QRegularExpression>#include <QStandardPaths>#include <QTimer>#include <QUrlQuery>#include <QCommandLineParser>#include <QUuid> #include <AccountManager.h>#include <AssetClient.h>#include <BuildInfo.h>#include <CrashAnnotations.h>#include <DependencyManager.h>#include <HifiConfigVariantMap.h>#include <HTTPConnection.h>#include <LogUtils.h>#include <NetworkingConstants.h>#include <MetaverseAPI.h>#include <udt/PacketHeaders.h>#include <SettingHandle.h>#include <SharedUtil.h>#include <ShutdownEventListener.h>#include <UUID.h>#include <LogHandler.h>#include <PathUtils.h>#include <NumericalConstants.h>#include <Trace.h>#include <StatTracker.h> #include "AssetsBackupHandler.h"#include "ContentSettingsBackupHandler.h"#include "DomainServerNodeData.h"#include "EntitiesBackupHandler.h"#include "NodeConnectionData.h" #include <Gzip.h> #include <OctreeDataUtils.h>#include <ThreadHelpers.h>#include <crash-handler/CrashHandler.h>  using namespace std::chrono; Q_LOGGING_CATEGORY(domain_server, "hifi.domain_server")Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")Q_LOGGING_CATEGORY(domain_server_auth, "overte.domain_server.auth") const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";const QString& DOMAIN_SERVER_SETTINGS_KEY = "domain_server";const QString PUBLIC_SOCKET_ADDRESS_KEY = "network_address";const QString PUBLIC_SOCKET_PORT_KEY = "network_port";const int MIN_PORT = 1;const int MAX_PORT = 65535; int const DomainServer::EXIT_CODE_REBOOT = 234923; QString DomainServer::_iceServerAddr { NetworkingConstants::ICE_SERVER_DEFAULT_HOSTNAME };int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT };bool DomainServer::_overrideDomainID { false };QUuid DomainServer::_overridingDomainID;bool DomainServer::_getTempName { false };QString DomainServer::_userConfigFilename;int DomainServer::_parentPID { -1 };bool DomainServer::_forceCrashReporting{false};  /// @brief The Domain server can proxy requests to the Directory Server, this function handles those forwarding requests./// @param connection The HTTP connection object./// @param requestUrl The full URL of the request. e.g. https://google.com/api/v1/test/// @param metaversePath The path on the Directory Server to route to./// @param requestSubobjectKey (Optional) The parent object key that any data will be inserted into for the forwarded request./// @param requiredData (Optional) This data is required to be present for the request./// @param optionalData (Optional) If provided, this optional data will be forwarded with the request./// @param requireAccessToken Require a valid access token to be sent with this request.bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,                                              const QUrl& requestUrl,                                              const QString& metaversePath,                                              const QString& requestSubobjectKey,                                              std::initializer_list<QString> requiredData,                                              std::initializer_list<QString> optionalData,                                              bool requireAccessToken) {     auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);    if (!accessTokenVariant.isValid() && requireAccessToken) {        connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");        return true;    }     QJsonObject subobject;     if (requestUrl.hasQuery()) {        QUrlQuery query(requestUrl);         for (auto& key : requiredData) {            if (query.hasQueryItem(key)) {                subobject.insert(key, query.queryItemValue(key));            } else {                auto error = "Domain Server: Bad request, expected param '" + key + "'";                connection->respond(HTTPConnection::StatusCode400, error.toLatin1());                return true;            }        }         for (auto& key : optionalData) {            if (query.hasQueryItem(key)) {                subobject.insert(key, query.queryItemValue(key));            }        }    } else {        auto params = connection->parseUrlEncodedForm();         for (auto& key : requiredData) {            auto it = params.find(key);            if (it == params.end()) {                auto error = "Domain Server: Bad request, expected param '" + key + "'";                connection->respond(HTTPConnection::StatusCode400, error.toLatin1());                return true;            }            subobject.insert(key, it.value());        }         for (auto& key : optionalData) {            auto it = params.find(key);            if (it != params.end()) {                subobject.insert(key, it.value());            }        }    }     QJsonObject root;    root.insert(requestSubobjectKey, subobject);    QJsonDocument doc { root };     QUrl url { MetaverseAPI::getCurrentMetaverseServerURL().toString() + metaversePath };     QNetworkRequest req(url);    req.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::OVERTE_USER_AGENT);    req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");     if (accessTokenVariant.isValid()) {        auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString();        req.setRawHeader("Authorization", accessTokenHeader.toLatin1());    }     QNetworkReply* reply;    auto method = connection->requestOperation();    if (method == QNetworkAccessManager::GetOperation) {        reply = NetworkAccessManager::getInstance().get(req);    } else if (method == QNetworkAccessManager::PostOperation) {        reply = NetworkAccessManager::getInstance().post(req, doc.toJson());    } else if (method == QNetworkAccessManager::PutOperation) {        reply = NetworkAccessManager::getInstance().put(req, doc.toJson());    } else {        connection->respond(HTTPConnection::StatusCode400, "Error forwarding request, unsupported method");        return true;    }     connect(reply, &QNetworkReply::finished, this, [reply, connection]() {        if (reply->error() != QNetworkReply::NoError) {            auto data = reply->readAll();            qDebug() << "Got error response from directory server (" << reply->url() << "): " << data << reply->errorString();            connection->respond(HTTPConnection::StatusCode400, data);            return;        }         connection->respond(HTTPConnection::StatusCode200, reply->readAll());    });     return true;} DomainServer::DomainServer(int argc, char* argv[]) :    QCoreApplication(argc, argv),    _gatekeeper(this),    _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT,        QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this){    static const QString CRASH_REPORTER = "crash_reporting.enable_crash_reporter";     if (_parentPID != -1) {        watchParentProcess(_parentPID);    }     PathUtils::removeTemporaryApplicationDirs();     DependencyManager::set<tracing::Tracer>();    DependencyManager::set<StatTracker>();     LogUtils::init();     LogHandler::getInstance().moveToThread(thread());    LogHandler::getInstance().setupRepeatedMessageFlusher();     qDebug() << "Setting up domain-server";    qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());    qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;    qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION;    qDebug() << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING;    qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;    qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr;     connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit);     // make sure we have a fresh AccountManager instance    // (need this since domain-server can restart itself and maintain static variables)    DependencyManager::set<AccountManager>();     // load the user config    QString userConfigFilename;    if (!_userConfigFilename.isEmpty()) {        userConfigFilename = _userConfigFilename;    } else {        // we weren't passed a user config path        static const QString USER_CONFIG_FILE_NAME = "config.json";        userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME);    }    _settingsManager.setupConfigMap(userConfigFilename);     // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32    installNativeEventFilter(&ShutdownEventListener::getInstance());#else    ShutdownEventListener::getInstance();#endif     auto &ch = CrashHandler::getInstance();    ch.setEnabled(_settingsManager.valueOrDefaultValueForKeyPath(CRASH_REPORTER).toBool());     qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");    qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");     // make sure we hear about newly connected nodes from our gatekeeper    connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);     // if a connected node loses connection privileges, hang up on it    connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode);     // if permissions are updated, relay the changes to the Node datastructures    connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,            &_gatekeeper, &DomainGatekeeper::updateNodePermissions);    connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,            this, &DomainServer::updateReplicatedNodes);    connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,            this, &DomainServer::updateDownstreamNodes);    connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,            this, &DomainServer::updateUpstreamNodes);    setupGroupCacheRefresh();     optionallySetupOAuth();     if (_oauthEnable) {        _oauthEnable = optionallyReadX509KeyAndCertificate();    }     _settingsManager.apiRefreshGroupInformation(); #if defined(WEBRTC_DATA_CHANNELS)    const QString WEBRTC_ENABLE = "webrtc.enable_webrtc";    bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_ENABLE).toBool();    qCDebug(domain_server) << "WebRTC enabled:" << isWebRTCEnabled;    // The domain server's WebRTC signaling server is used by the domain server and the assignment clients, so disabling it    // disables WebRTC for the server as a whole.    if (isWebRTCEnabled) {        const QString WEBRTC_WSS_ENABLE = "webrtc.enable_webrtc_websocket_ssl";        bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_WSS_ENABLE).toBool();        qCDebug(domain_server) << "WebRTC WSS enabled:" << isWebRTCEnabled;        _webrtcSignalingServer.reset(new WebRTCSignalingServer(this, isWebRTCEnabled));    }#endif     setupNodeListAndAssignments();     updateReplicatedNodes();    updateDownstreamNodes();    updateUpstreamNodes(); #if defined(WEBRTC_DATA_CHANNELS)    if (isWebRTCEnabled) {        setUpWebRTCSignalingServer();    }#endif     if (_type != NonMetaverse) {        // if we have a directory services domain, we'll use an access token for API calls        resetAccountManagerAccessToken();         setupAutomaticNetworking();    }     if (!getID().isNull() && _type != NonMetaverse) {        // setup periodic heartbeats to directory services API        setupHeartbeatToMetaverse();         // send the first heartbeat immediately        sendHeartbeatToMetaverse();    }     // check for the temporary name parameter    if (_getTempName) {        getTemporaryName();    }     // send signal to DomainMetadata when descriptors changed    _metadata = new DomainMetadata(this);    connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,            _metadata, &DomainMetadata::descriptorsChanged);     // update the metadata when a user (dis)connects    connect(this, &DomainServer::userConnected, _metadata, &DomainMetadata::usersChanged);    connect(this, &DomainServer::userDisconnected, _metadata, &DomainMetadata::usersChanged);     // update the metadata when security changes    connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, [this] { _metadata->securityChanged(true); });     qDebug() << "domain-server is running";    static const QString AC_SUBNET_ALLOWLIST_SETTING_PATH = "security.ac_subnet_allowlist";     static const Subnet LOCALHOST { QHostAddress("127.0.0.1"), 32 };    _acSubnetAllowlist = { LOCALHOST };     auto allowlist = _settingsManager.valueOrDefaultValueForKeyPath(AC_SUBNET_ALLOWLIST_SETTING_PATH).toStringList();    for (auto& subnet : allowlist) {        auto netmaskParts = subnet.trimmed().split("/");         if (netmaskParts.size() > 2) {            qDebug() << "Ignoring subnet in allowlist, malformed: " << subnet;            continue;        }         // The default netmask is 32 if one has not been specified, which will        // match only the ip provided.        int netmask = 32;         if (netmaskParts.size() == 2) {            bool ok;            netmask = netmaskParts[1].toInt(&ok);            if (!ok) {                qDebug() << "Ignoring subnet in allowlist, bad netmask: " << subnet;                continue;            }        }         auto ip = QHostAddress(netmaskParts[0]);         if (!ip.isNull()) {            qDebug() << "Adding AC allowlist subnet: " << subnet << " -> " << (ip.toString() + "/" + QString::number(netmask));            _acSubnetAllowlist.push_back({ ip , netmask });        } else {            qDebug() << "Ignoring subnet in allowlist, invalid ip portion: " << subnet;        }    }     if (QDir(getEntitiesDirPath()).mkpath(".")) {        qCDebug(domain_server) << "Created entities data directory";    }    maybeHandleReplacementEntityFile();     _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager));     connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){        _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));        _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled())));        _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager)));    });     _contentManager->initialize(true);     connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart);     static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND;    _nodePingMonitorTimer = new QTimer{ this };    connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor);    _nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS);     initializeExporter();    initializeMetadataExporter();} void DomainServer::parseCommandLine(int argc, char* argv[]) {    QCommandLineParser parser;    parser.setApplicationDescription("Overte Domain Server");    const QCommandLineOption versionOption = parser.addVersionOption();    const QCommandLineOption helpOption = parser.addHelpOption();     const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");    parser.addOption(iceServerAddressOption);     const QCommandLineOption domainIDOption("d", "domain-server uuid", "uuid");    parser.addOption(domainIDOption);     const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name");    parser.addOption(getTempNameOption);     const QCommandLineOption userConfigOption("user-config", "Pass user config file pass", "path");    parser.addOption(userConfigOption);     const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid");    parser.addOption(parentPIDOption);     const QCommandLineOption logOption("logOptions", "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options");    parser.addOption(logOption);     const QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize.");    parser.addOption(forceCrashReportingOption);     QStringList arguments;    for (int i = 0; i < argc; ++i) {        arguments << argv[i];    }    if (!parser.parse(arguments)) {        std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam        QCoreApplication mockApp(argc, argv); // required for call to showHelp()        parser.showHelp();        Q_UNREACHABLE();    }     if (parser.isSet(versionOption)) {        parser.showVersion();        Q_UNREACHABLE();    }    if (parser.isSet(helpOption)) {        QCoreApplication mockApp(argc, argv); // required for call to showHelp()        parser.showHelp();        Q_UNREACHABLE();    }     // We want to configure the logging system as early as possible    auto &logHandler = LogHandler::getInstance();    if (parser.isSet(logOption)) {        if (!logHandler.parseOptions(parser.value(logOption).toUtf8(), logOption.names().first())) {            QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()            parser.showHelp();            Q_UNREACHABLE();        }    }     if (parser.isSet(iceServerAddressOption)) {        // parse the IP and port combination for this target        QString hostnamePortString = parser.value(iceServerAddressOption);         _iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':'));        _iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt();        if (_iceServerPort == 0) {            _iceServerPort = ICE_SERVER_DEFAULT_PORT;        }         if (_iceServerAddr.isEmpty()) {            qCWarning(domain_server_ice) << "ALERT: Could not parse an IP address and port combination from" << hostnamePortString;            ::exit(0);        }    }     if (parser.isSet(domainIDOption)) {        _overridingDomainID = QUuid(parser.value(domainIDOption));        _overrideDomainID = true;        qDebug() << "domain-server ID is" << _overridingDomainID;    }     if (parser.isSet(getTempNameOption)) {        _getTempName = true;    }     if (parser.isSet(userConfigOption)) {        _userConfigFilename = parser.value(userConfigOption);    }     if (parser.isSet(parentPIDOption)) {        bool ok = false;        int parentPID = parser.value(parentPIDOption).toInt(&ok);         if (ok) {            _parentPID = parentPID;

Lines 1–500 of 3817.