mirror of
https://github.com/overte-org/overte.git
synced 2025-06-17 20:20:42 +02:00
311 lines
13 KiB
C++
311 lines
13 KiB
C++
//
|
|
// IceServer.cpp
|
|
// ice-server/src
|
|
//
|
|
// Created by Stephen Birarda on 2014-10-01.
|
|
// 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 "IceServer.h"
|
|
|
|
#include <openssl/x509.h>
|
|
|
|
#include <QtCore/QJsonDocument>
|
|
#include <QtCore/QTimer>
|
|
#include <QtNetwork/QNetworkReply>
|
|
#include <QtNetwork/QNetworkRequest>
|
|
|
|
#include <LimitedNodeList.h>
|
|
#include <NetworkAccessManager.h>
|
|
#include <NetworkingConstants.h>
|
|
#include <udt/PacketHeaders.h>
|
|
#include <SharedUtil.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()
|
|
{
|
|
// start the ice-server socket
|
|
qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT;
|
|
_serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_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
|
|
HifiSockAddr 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;
|
|
HifiSockAddr 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) {
|
|
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);
|
|
|
|
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 metaverse 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 metaverse API for the public key for this domain
|
|
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
|
|
|
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL() };
|
|
QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));
|
|
publicKeyURL.setPath(publicKeyPath);
|
|
|
|
QNetworkRequest publicKeyRequest { publicKeyURL };
|
|
publicKeyRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
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());
|
|
|
|
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.";
|
|
}
|
|
|
|
} else {
|
|
qWarning() << "There was no public key present in response for domain with ID" << domainID;
|
|
}
|
|
} else {
|
|
qWarning() << "The metaverse 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 HifiSockAddr* 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;
|
|
}
|
|
}
|
|
}
|