//// 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; } }}
//
// 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;
}
}
}