HifiExperimentsenable warnings as errors6ff5e171 year agoHistory
382 lines · 16.1 KB · C++
RawDownload
////  IceServer.cpp//  ice-server/src////  Created by Stephen Birarda on 2014-10-01.//  Copyright 2014 High Fidelity, Inc.//  Copyright 2021 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 "IceServer.h" #include <openssl/x509.h> #include <QtCore/QDataStream>#include <QtCore/QJsonDocument>#include <QtCore/QJsonObject>#include <QtCore/QTimer>#include <QtNetwork/QNetworkReply>#include <QtNetwork/QNetworkRequest>#include <QtCore/QSharedPointer>#include <QCommandLineParser> #include <LimitedNodeList.h>#include <NetworkAccessManager.h>#include <NetworkingConstants.h>#include <MetaverseAPI.h>#include <udt/PacketHeaders.h>#include <SharedUtil.h>#include "WarningsSuppression.h"#include <LogHandler.h> const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; IceServer::IceServer(int argc, char* argv[]) :    QCoreApplication(argc, argv),    _id(QUuid::createUuid()),    _serverSocket(0, false),    _activePeers(){    // parse command-line    QCommandLineParser parser;     const QCommandLineOption helpOption = parser.addHelpOption();     const QCommandLineOption addressOption("address", "Ice listen address.", "address");    parser.addOption(addressOption);     const QCommandLineOption portOption("port", "Port for the ICE server to listen on", "port");    parser.addOption(portOption);     const QCommandLineOption logOption("logOptions",  "Logging options, comma separated: ", "color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options");    parser.addOption(logOption);     if (!parser.parse(QCoreApplication::arguments())) {        qCritical() << parser.errorText() << Qt::endl;        parser.showHelp();        Q_UNREACHABLE();    }     if (parser.isSet(helpOption)) {        parser.showHelp();        Q_UNREACHABLE();    }     QHostAddress address = QHostAddress::AnyIPv4;    if (parser.isSet(addressOption)) {        if (parser.value(addressOption) == "0.0.0.0") {            QHostAddress address = QHostAddress::AnyIPv4;        } else {            address.setAddress(parser.value(addressOption));        }    }     // set the port    quint16 port = ICE_SERVER_DEFAULT_PORT;    if (parser.isSet(portOption)) {        port = parser.value(portOption).toInt();    }     // 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();        }    }      _serverSocket.bind(SocketType::UDP, address, port);    qDebug() << "ice-server socket is listening on address: " << address.toString() << ":" << port;     // set processPacket as the verified packet callback for the udt::Socket    _serverSocket.setPacketHandler([this](std::unique_ptr<udt::Packet> packet) { processPacket(std::move(packet));  });     // set packetVersionMatch as the verify packet operator for the udt::Socket    using std::placeholders::_1;    _serverSocket.setPacketFilterOperator(std::bind(&IceServer::packetVersionMatch, this, _1));     // setup our timer to clear inactive peers    QTimer* inactivePeerTimer = new QTimer(this);    connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);    inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);     // handle public keys when they arrive from the QNetworkAccessManager    auto& networkAccessManager = NetworkAccessManager::getInstance();    connect(&networkAccessManager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished);} bool IceServer::packetVersionMatch(const udt::Packet& packet) {    PacketType headerType = NLPacket::typeInHeader(packet);    PacketVersion headerVersion = NLPacket::versionInHeader(packet);     if (headerVersion == versionForPacketType(headerType)) {        return true;    } else {        return false;    }} void IceServer::processPacket(std::unique_ptr<udt::Packet> packet) {     auto nlPacket = NLPacket::fromBase(std::move(packet));     // make sure that this packet at least looks like something we can read    if (nlPacket->getPayloadSize() >= NLPacket::localHeaderSize(PacketType::ICEServerHeartbeat)) {         if (nlPacket->getType() == PacketType::ICEServerHeartbeat) {            SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket);            if (peer) {                // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now                peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());                 // we have an active and verified heartbeating peer                // send them an ACK packet so they know that they are being heard and ready for ICE                static auto ackPacket = NLPacket::create(PacketType::ICEServerHeartbeatACK);                _serverSocket.writePacket(*ackPacket, nlPacket->getSenderSockAddr());            } else {                // we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation                static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied);                _serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr());            }        } else if (nlPacket->getType() == PacketType::ICEServerQuery) {            QDataStream heartbeatStream(nlPacket.get());             // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer?            QUuid senderUUID;            heartbeatStream >> senderUUID;             // pull the public and private sock addrs for this peer            SockAddr publicSocket, localSocket;            heartbeatStream >> publicSocket >> localSocket;             // check if this node also included a UUID that they would like to connect to            QUuid connectRequestID;            heartbeatStream >> connectRequestID;             SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID);             if (matchingPeer) {                 qDebug() << "Sending information for peer" << connectRequestID << "to peer" << senderUUID;                 // we have the peer they want to connect to - send them pack the information for that peer                sendPeerInformationPacket(*matchingPeer, &nlPacket->getSenderSockAddr());                 // we also need to send them to the active peer they are hoping to connect to                // create a dummy peer object we can pass to sendPeerInformationPacket                 NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket);                sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket());            } else {                qDebug() << "Peer" << senderUUID << "asked for" << connectRequestID << "but no matching peer found";            }        }    }} SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) {     // pull the UUID, public and private sock addrs for this peer    QUuid senderUUID;    SockAddr publicSocket, localSocket;    QByteArray signature;     QDataStream heartbeatStream(&packet);    heartbeatStream >> senderUUID >> publicSocket >> localSocket;     auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos());    heartbeatStream >> signature;     // make sure this is a verified heartbeat before performing any more processing    if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) {        // make sure we have this sender in our peer hash        SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);         if (!matchingPeer) {            // if we don't have this sender we need to create them now            matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);            _activePeers.insert(senderUUID, matchingPeer);             qDebug() << "Added a new network peer" << *matchingPeer;        } else {            // we already had the peer so just potentially update their sockets            matchingPeer->setPublicSocket(publicSocket);            matchingPeer->setLocalSocket(localSocket);        }         // update our last heard microstamp for this network peer to now        matchingPeer->setLastHeardMicrostamp(usecTimestampNow());         return matchingPeer;    } else {        // not verified, return the empty peer object        return SharedNetworkPeer();    }} bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) {    // make sure we're not already waiting for a public key for this domain-server    if (!_pendingPublicKeyRequests.contains(domainID)) {        // check if we have a public key for this domain ID - if we do not then fire off the request for it        auto it = _domainPublicKeys.find(domainID);        if (it != _domainPublicKeys.end()) {             // attempt to verify the signature for this heartbeat            const auto rsaPublicKey = it->second.get();             if (rsaPublicKey) {                OVERTE_IGNORE_DEPRECATED_BEGIN                 auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);                int verificationResult = RSA_verify(NID_sha256,                                                    reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),                                                    hashedPlaintext.size(),                                                    reinterpret_cast<const unsigned char*>(signature.constData()),                                                    signature.size(),                                                    rsaPublicKey);                 OVERTE_IGNORE_WARNING_END                if (verificationResult == 1) {                    // this is the only success case - we return true here to indicate that the heartbeat is verified                    return true;                } else {                    qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API.";                }             } else {                // we can't let this user in since we couldn't convert their public key to an RSA key we could use                qWarning() << "Public key for" << domainID << "is not a usable RSA* public key.";                qWarning() << "Re-requesting public key from API";            }        }         // we could not verify this heartbeat (missing public key, could not load public key, bad actor)        // ask the directory services API for the right public key and return false to indicate that this is not verified        requestDomainPublicKey(domainID);    }     return false;} void IceServer::requestDomainPublicKey(const QUuid& domainID) {    // send a request to the directory services API for the public key for this domain    auto& networkAccessManager = NetworkAccessManager::getInstance();     QUrl publicKeyURL{ MetaverseAPI::getCurrentMetaverseServerURL() };    // qDebug() << "publicKeyURL" << publicKeyURL;    // qDebug() << "MetaverseAPI::getCurrentMetaverseServerURLPath()" << MetaverseAPI::getCurrentMetaverseServerURLPath();    QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));    publicKeyURL.setPath("/" + MetaverseAPI::getCurrentMetaverseServerURLPath() + publicKeyPath);    // qDebug() << "publicKeyPath" << publicKeyPath;    // qDebug() << "publicKeyURL.setPath" << "/" + MetaverseAPI::getCurrentMetaverseServerURLPath() + publicKeyPath;    // qDebug() << "publicKeyURL" << publicKeyURL;    // qDebug() << "publicKeyURL.isValid()" << publicKeyURL.isValid();    // qDebug() << "publicKeyURL.errorString()" << publicKeyURL.errorString();     QNetworkRequest publicKeyRequest { publicKeyURL };    publicKeyRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);    publicKeyRequest.setAttribute(QNetworkRequest::User, domainID);     qDebug() << "Requesting public key for domain with ID" << domainID;     // add this to the set of pending public key requests    _pendingPublicKeyRequests.insert(domainID);     networkAccessManager.get(publicKeyRequest);} void IceServer::publicKeyReplyFinished(QNetworkReply* reply) {    // get the domain ID from the QNetworkReply attribute    QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid();     if (reply->error() == QNetworkReply::NoError) {        // pull out the public key and store it for this domain         // the response should be JSON        QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll());         static const QString DATA_KEY = "data";        static const QString PUBLIC_KEY_KEY = "public_key";        static const QString STATUS_KEY = "status";        static const QString SUCCESS_VALUE = "success";         auto responseObject = responseDocument.object();        if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) {            auto dataObject = responseObject[DATA_KEY].toObject();            if (dataObject.contains(PUBLIC_KEY_KEY)) {                 // grab the base 64 public key from the API response                auto apiPublicKey = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8());                 // convert the downloaded public key to an RSA struct, if possible                const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(apiPublicKey.constData());                 OVERTE_IGNORE_DEPRECATED_BEGIN                RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, apiPublicKey.size());                 if (rsaPublicKey) {                    _domainPublicKeys[domainID] = { rsaPublicKey, RSA_free };                } else {                    qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key.";                    qWarning() << "Public key will be re-requested on next heartbeat.";                }                OVERTE_IGNORE_WARNING_END             } else {                qWarning() << "There was no public key present in response for domain with ID" << domainID;            }        } else {            qWarning() << "The directory services API did not return success for public key request for domain with ID" << domainID;        }     } else {        // there was a problem getting the public key for the domain        // log it since it will be re-requested on the next heartbeat         qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" <<  reply->errorString();    }     // remove this domain ID from the list of pending public key requests    _pendingPublicKeyRequests.remove(domainID);     reply->deleteLater();} void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const SockAddr* destinationSockAddr) {    auto peerPacket = NLPacket::create(PacketType::ICEServerPeerInformation);     // get the byte array for this peer    peerPacket->write(peer.toByteArray());     // write the current packet    _serverSocket.writePacket(*peerPacket, *destinationSockAddr);} void IceServer::clearInactivePeers() {    NetworkPeerHash::iterator peerItem = _activePeers.begin();     while (peerItem != _activePeers.end()) {        SharedNetworkPeer peer = peerItem.value();         if ((usecTimestampNow() - peer->getLastHeardMicrostamp()) > (PEER_SILENCE_THRESHOLD_MSECS * 1000)) {            qDebug() << "Removing peer from memory for inactivity -" << *peer;             // if we had a public key for this domain, remove it now            _domainPublicKeys.erase(peer->getUUID());             // remove the peer object            peerItem = _activePeers.erase(peerItem);        } else {            // we didn't kill this peer, push the iterator forwards            ++peerItem;        }    }}