overte/libraries/networking/src/LimitedNodeList.cpp
Simon Walton 8ce03d65b7 Only update the connection secret UUID if it changes
Rekeying the openssl HMAC context occasionally causes
hash generation errors. It is not clear why. The Node
secret never seems to change to check for this before
rekeying. Also other clean-up for PR.
2018-03-20 18:16:45 -07:00

1242 lines
49 KiB
C++

//
// LimitedNodeList.cpp
// libraries/networking/src
//
// Created by Stephen Birarda on 2/15/13.
// Copyright 2013 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 "LimitedNodeList.h"
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <QtCore/QDataStream>
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QThread>
#include <QtCore/QUrl>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QHostInfo>
#include <LogHandler.h>
#include <shared/NetworkUtils.h>
#include <NumericalConstants.h>
#include <SettingHandle.h>
#include <SharedUtil.h>
#include <UUID.h>
#include "AccountManager.h"
#include "AssetClient.h"
#include "Assignment.h"
#include "HifiSockAddr.h"
#include "NetworkLogging.h"
#include "udt/Packet.h"
#include "HmacAuth.h"
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
const std::set<NodeType_t> SOLO_NODE_TYPES = {
NodeType::AvatarMixer,
NodeType::AudioMixer,
NodeType::AssetServer,
NodeType::EntityServer,
NodeType::MessagesMixer,
NodeType::EntityScriptServer
};
LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) :
_nodeSocket(this),
_packetReceiver(new PacketReceiver(this))
{
qRegisterMetaType<ConnectionStep>("ConnectionStep");
auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get();
_nodeSocket.bind(QHostAddress::AnyIPv4, port);
qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort();
if (dtlsListenPort != INVALID_PORT) {
// only create the DTLS socket during constructor if a custom port is passed
_dtlsSocket = new QUdpSocket(this);
_dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort);
qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
// check for local socket updates every so often
const int LOCAL_SOCKET_UPDATE_INTERVAL_MSECS = 5 * 1000;
QTimer* localSocketUpdate = new QTimer(this);
connect(localSocketUpdate, &QTimer::timeout, this, &LimitedNodeList::updateLocalSocket);
localSocketUpdate->start(LOCAL_SOCKET_UPDATE_INTERVAL_MSECS);
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, &QTimer::timeout, this, &LimitedNodeList::removeSilentNodes);
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS);
// check the local socket right now
updateLocalSocket();
// set &PacketReceiver::handleVerifiedPacket as the verified packet callback for the udt::Socket
_nodeSocket.setPacketHandler([this](std::unique_ptr<udt::Packet> packet) {
_packetReceiver->handleVerifiedPacket(std::move(packet));
});
_nodeSocket.setMessageHandler([this](std::unique_ptr<udt::Packet> packet) {
_packetReceiver->handleVerifiedMessagePacket(std::move(packet));
});
_nodeSocket.setMessageFailureHandler([this](HifiSockAddr from,
udt::Packet::MessageNumber messageNumber) {
_packetReceiver->handleMessageFailure(from, messageNumber);
});
// set our isPacketVerified method as the verify operator for the udt::Socket
using std::placeholders::_1;
_nodeSocket.setPacketFilterOperator(std::bind(&LimitedNodeList::isPacketVerified, this, _1));
// set our socketBelongsToNode method as the connection creation filter operator for the udt::Socket
_nodeSocket.setConnectionCreationFilterOperator(std::bind(&LimitedNodeList::sockAddrBelongsToNode, this, _1));
// handle when a socket connection has its receiver side reset - might need to emit clientConnectionToNodeReset
connect(&_nodeSocket, &udt::Socket::clientHandshakeRequestComplete, this, &LimitedNodeList::clientConnectionToSockAddrReset);
_packetStatTimer.start();
if (_stunSockAddr.getAddress().isNull()) {
// we don't know the stun server socket yet, add it to unfiltered once known
connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::addSTUNHandlerToUnfiltered);
} else {
// we know the stun server socket, add it to unfiltered now
addSTUNHandlerToUnfiltered();
}
}
QUuid LimitedNodeList::getSessionUUID() const {
QReadLocker lock { &_sessionUUIDLock };
return _sessionUUID;
}
void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) {
QUuid oldUUID;
{
QWriteLocker lock { &_sessionUUIDLock };
oldUUID = _sessionUUID;
_sessionUUID = sessionUUID;
}
if (sessionUUID != oldUUID) {
qCDebug(networking) << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID)
<< "to" << uuidStringWithoutCurlyBraces(sessionUUID);
emit uuidChanged(sessionUUID, oldUUID);
}
}
void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
NodePermissions originalPermissions = _permissions;
_permissions = newPermissions;
if (originalPermissions.can(NodePermissions::Permission::canAdjustLocks) !=
newPermissions.can(NodePermissions::Permission::canAdjustLocks)) {
emit isAllowedEditorChanged(_permissions.can(NodePermissions::Permission::canAdjustLocks));
}
if (originalPermissions.can(NodePermissions::Permission::canRezPermanentEntities) !=
newPermissions.can(NodePermissions::Permission::canRezPermanentEntities)) {
emit canRezChanged(_permissions.can(NodePermissions::Permission::canRezPermanentEntities));
}
if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryEntities) !=
newPermissions.can(NodePermissions::Permission::canRezTemporaryEntities)) {
emit canRezTmpChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryEntities));
}
if (originalPermissions.can(NodePermissions::Permission::canRezPermanentCertifiedEntities) !=
newPermissions.can(NodePermissions::Permission::canRezPermanentCertifiedEntities)) {
emit canRezCertifiedChanged(_permissions.can(NodePermissions::Permission::canRezPermanentCertifiedEntities));
}
if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryCertifiedEntities) !=
newPermissions.can(NodePermissions::Permission::canRezTemporaryCertifiedEntities)) {
emit canRezTmpCertifiedChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryCertifiedEntities));
}
if (originalPermissions.can(NodePermissions::Permission::canWriteToAssetServer) !=
newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) {
emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer));
}
if (originalPermissions.can(NodePermissions::Permission::canKick) !=
newPermissions.can(NodePermissions::Permission::canKick)) {
emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick));
}
if (originalPermissions.can(NodePermissions::Permission::canReplaceDomainContent) !=
newPermissions.can(NodePermissions::Permission::canReplaceDomainContent)) {
emit canReplaceContentChanged(_permissions.can(NodePermissions::Permission::canReplaceDomainContent));
}
}
void LimitedNodeList::setSocketLocalPort(quint16 socketLocalPort) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSocketLocalPort", Qt::QueuedConnection,
Q_ARG(quint16, socketLocalPort));
return;
}
if (_nodeSocket.localPort() != socketLocalPort) {
_nodeSocket.rebind(socketLocalPort);
LIMITED_NODELIST_LOCAL_PORT.set(socketLocalPort);
}
}
QUdpSocket& LimitedNodeList::getDTLSSocket() {
if (!_dtlsSocket) {
// DTLS socket getter called but no DTLS socket exists, create it now
_dtlsSocket = new QUdpSocket(this);
_dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress);
// we're using DTLS and our socket is good to go, so make the required DTLS changes
// DTLS requires that IP_DONTFRAG be set
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
qCDebug(networking) << "LimitedNodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
return *_dtlsSocket;
}
bool LimitedNodeList::isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode) {
// We track bandwidth when doing packet verification to avoid needing to do a node lookup
// later when we already do it in packetSourceAndHashMatchAndTrackBandwidth. A node lookup
// incurs a lock, so it is ideal to avoid needing to do it 2+ times for each packet
// received.
return packetVersionMatch(packet) && packetSourceAndHashMatchAndTrackBandwidth(packet, sourceNode);
}
bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) {
PacketType headerType = NLPacket::typeInHeader(packet);
PacketVersion headerVersion = NLPacket::versionInHeader(packet);
if (headerVersion != versionForPacketType(headerType)) {
static QMultiHash<QUuid, PacketType> sourcedVersionDebugSuppressMap;
static QMultiHash<HifiSockAddr, PacketType> versionDebugSuppressMap;
bool hasBeenOutput = false;
QString senderString;
const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr();
QUuid sourceID;
if (PacketTypeEnum::getNonSourcedPackets().contains(headerType)) {
hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, headerType);
if (!hasBeenOutput) {
versionDebugSuppressMap.insert(senderSockAddr, headerType);
senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort());
}
} else {
sourceID = NLPacket::sourceIDInHeader(packet);
hasBeenOutput = sourcedVersionDebugSuppressMap.contains(sourceID, headerType);
if (!hasBeenOutput) {
sourcedVersionDebugSuppressMap.insert(sourceID, headerType);
senderString = uuidStringWithoutCurlyBraces(sourceID.toString());
}
}
if (!hasBeenOutput) {
qCDebug(networking) << "Packet version mismatch on" << headerType << "- Sender"
<< senderString << "sent" << qPrintable(QString::number(headerVersion)) << "but"
<< qPrintable(QString::number(versionForPacketType(headerType))) << "expected.";
emit packetVersionMismatch(headerType, senderSockAddr, sourceID);
}
return false;
} else {
return true;
}
}
bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet, Node* sourceNode) {
PacketType headerType = NLPacket::typeInHeader(packet);
if (PacketTypeEnum::getNonSourcedPackets().contains(headerType)) {
if (PacketTypeEnum::getReplicatedPacketMapping().key(headerType) != PacketType::Unknown) {
// this is a replicated packet type - make sure the socket that sent it to us matches
// one from one of our current upstream nodes
NodeType_t sendingNodeType { NodeType::Unassigned };
eachNodeBreakable([&packet, &sendingNodeType](const SharedNodePointer& node){
if (NodeType::isUpstream(node->getType()) && node->getPublicSocket() == packet.getSenderSockAddr()) {
sendingNodeType = node->getType();
return false;
} else {
return true;
}
});
if (sendingNodeType != NodeType::Unassigned) {
emit dataReceived(sendingNodeType, packet.getPayloadSize());
return true;
} else {
static const QString UNSOLICITED_REPLICATED_REGEX =
"Replicated packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown upstream";
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex(UNSOLICITED_REPLICATED_REGEX);
qCDebug(networking) << "Replicated packet of type" << headerType
<< "received from unknown upstream" << packet.getSenderSockAddr();
return false;
}
} else {
emit dataReceived(NodeType::Unassigned, packet.getPayloadSize());
return true;
}
} else {
QUuid sourceID = NLPacket::sourceIDInHeader(packet);
// check if we were passed a sourceNode hint or if we need to look it up
if (!sourceNode) {
// figure out which node this is from
SharedNodePointer matchingNode = nodeWithUUID(sourceID);
sourceNode = matchingNode.data();
}
if (!sourceNode &&
sourceID == getDomainUUID() &&
packet.getSenderSockAddr() == getDomainSockAddr() &&
PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) {
// This is a packet sourced by the domain server
emit dataReceived(NodeType::Unassigned, packet.getPayloadSize());
return true;
}
if (sourceNode) {
bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType);
bool ignoreVerification = isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType);
if (verifiedPacket && !ignoreVerification) {
QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet);
QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getAuthenticateHash());
// check if the md5 hash in the header matches the hash we would expect
if (packetHeaderHash != expectedHash) {
static QMultiMap<QUuid, PacketType> hashDebugSuppressMap;
if (!hashDebugSuppressMap.contains(sourceID, headerType)) {
qCDebug(networking) << packetHeaderHash << expectedHash;
qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID;
hashDebugSuppressMap.insert(sourceID, headerType);
}
return false;
}
}
// No matter if this packet is handled or not, we update the timestamp for the last time we heard
// from this sending node
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
emit dataReceived(sourceNode->getType(), packet.getPayloadSize());
return true;
} else {
static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID";
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX);
qCDebug(networking) << "Packet of type" << headerType
<< "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID);
}
}
return false;
}
void LimitedNodeList::collectPacketStats(const NLPacket& packet) {
// stat collection for packets
++_numCollectedPackets;
_numCollectedBytes += packet.getDataSize();
}
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HmacAuth * hmacAuth) {
if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) {
packet.writeSourceID(getSessionUUID());
}
if (hmacAuth
&& !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())
&& !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) {
packet.writeVerificationHashGivenSecret(*hmacAuth);
}
}
static const qint64 ERROR_SENDING_PACKET_BYTES = -1;
qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode) {
Q_ASSERT(!packet.isPartOfMessage());
if (!destinationNode.getActiveSocket()) {
return 0;
}
emit dataSent(destinationNode.getType(), packet.getDataSize());
destinationNode.recordBytesSent(packet.getDataSize());
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), &destinationNode.getAuthenticateHash());
}
qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
HmacAuth * hmacAuth) {
Q_ASSERT(!packet.isPartOfMessage());
Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket",
"Trying to send a reliable packet unreliably.");
collectPacketStats(packet);
fillPacketHeader(packet, hmacAuth);
return _nodeSocket.writePacket(packet, sockAddr);
}
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode) {
Q_ASSERT(!packet->isPartOfMessage());
auto activeSocket = destinationNode.getActiveSocket();
if (activeSocket) {
emit dataSent(destinationNode.getType(), packet->getDataSize());
destinationNode.recordBytesSent(packet->getDataSize());
return sendPacket(std::move(packet), *activeSocket, &destinationNode.getAuthenticateHash());
} else {
qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending";
return ERROR_SENDING_PACKET_BYTES;
}
}
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
HmacAuth * hmacAuth) {
Q_ASSERT(!packet->isPartOfMessage());
if (packet->isReliable()) {
collectPacketStats(*packet);
fillPacketHeader(*packet, hmacAuth);
auto size = packet->getDataSize();
_nodeSocket.writePacket(std::move(packet), sockAddr);
return size;
} else {
return sendUnreliablePacket(*packet, sockAddr, hmacAuth);
}
}
qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode) {
auto activeSocket = destinationNode.getActiveSocket();
if (activeSocket) {
qint64 bytesSent = 0;
auto& connectionHash = destinationNode.getAuthenticateHash();
// close the last packet in the list
packetList.closeCurrentPacket();
while (!packetList._packets.empty()) {
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket,
&connectionHash);
}
emit dataSent(destinationNode.getType(), bytesSent);
return bytesSent;
} else {
qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node" << destinationNode
<< " - not sending.";
return ERROR_SENDING_PACKET_BYTES;
}
}
qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
HmacAuth * hmacAuth) {
qint64 bytesSent = 0;
// close the last packet in the list
packetList.closeCurrentPacket();
while (!packetList._packets.empty()) {
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, hmacAuth);
}
return bytesSent;
}
qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList, const HifiSockAddr& sockAddr) {
// close the last packet in the list
packetList->closeCurrentPacket();
for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) {
NLPacket* nlPacket = static_cast<NLPacket*>(packet.get());
collectPacketStats(*nlPacket);
fillPacketHeader(*nlPacket, nullptr);
}
return _nodeSocket.writePacketList(std::move(packetList), sockAddr);
}
qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList, const Node& destinationNode) {
auto activeSocket = destinationNode.getActiveSocket();
if (activeSocket) {
// close the last packet in the list
packetList->closeCurrentPacket();
for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) {
NLPacket* nlPacket = static_cast<NLPacket*>(packet.get());
collectPacketStats(*nlPacket);
fillPacketHeader(*nlPacket, &destinationNode.getAuthenticateHash());
}
return _nodeSocket.writePacketList(std::move(packetList), *activeSocket);
} else {
qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node "
<< destinationNode.getUUID() << ". Not sending.";
return ERROR_SENDING_PACKET_BYTES;
}
}
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode,
const HifiSockAddr& overridenSockAddr) {
if (overridenSockAddr.isNull() && !destinationNode.getActiveSocket()) {
qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node"
<< destinationNode.getUUID() << ". Not sending.";
return ERROR_SENDING_PACKET_BYTES;
}
// use the node's active socket as the destination socket if there is no overriden socket address
auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket()
: overridenSockAddr;
return sendPacket(std::move(packet), destinationSockAddr, &destinationNode.getAuthenticateHash());
}
int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
NodeData* linkedData = getOrCreateLinkedData(sendingNode);
if (linkedData) {
QMutexLocker linkedDataLocker(&linkedData->getMutex());
return linkedData->parseData(*message);
}
return 0;
}
NodeData* LimitedNodeList::getOrCreateLinkedData(SharedNodePointer node) {
QMutexLocker locker(&node->getMutex());
NodeData* linkedData = node->getLinkedData();
if (!linkedData && linkedDataCreateCallback) {
linkedDataCreateCallback(node.data());
}
return node->getLinkedData();
}
SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) {
QReadLocker readLocker(&_nodeMutex);
NodeHash::const_iterator it = _nodeHash.find(nodeUUID);
return it == _nodeHash.cend() ? SharedNodePointer() : it->second;
}
void LimitedNodeList::eraseAllNodes() {
QSet<SharedNodePointer> killedNodes;
{
// iterate the current nodes - grab them so we can emit that they are dying
// and then remove them from the hash
QWriteLocker writeLocker(&_nodeMutex);
if (_nodeHash.size() > 0) {
qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList.";
auto it = _nodeHash.begin();
while (it != _nodeHash.end()) {
killedNodes.insert(it->second);
it = _nodeHash.unsafe_erase(it);
}
}
}
foreach(const SharedNodePointer& killedNode, killedNodes) {
handleNodeKill(killedNode);
}
}
void LimitedNodeList::reset() {
eraseAllNodes();
// we need to make sure any socket connections are gone so wait on that here
_nodeSocket.clearConnections();
}
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
QReadLocker readLocker(&_nodeMutex);
NodeHash::iterator it = _nodeHash.find(nodeUUID);
if (it != _nodeHash.end()) {
SharedNodePointer matchingNode = it->second;
readLocker.unlock();
{
QWriteLocker writeLocker(&_nodeMutex);
_nodeHash.unsafe_erase(it);
}
handleNodeKill(matchingNode);
return true;
}
return false;
}
void LimitedNodeList::processKillNode(ReceivedMessage& message) {
// read the node id
QUuid nodeUUID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
// kill the node with this UUID, if it exists
killNodeWithUUID(nodeUUID);
}
void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
qCDebug(networking) << "Killed" << *node;
node->stopPingTimer();
emit nodeKilled(node);
if (auto activeSocket = node->getActiveSocket()) {
_nodeSocket.cleanupConnection(*activeSocket);
}
}
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool isReplicated, bool isUpstream,
const QUuid& connectionSecret, const NodePermissions& permissions) {
QReadLocker readLocker(&_nodeMutex);
NodeHash::const_iterator it = _nodeHash.find(uuid);
if (it != _nodeHash.end()) {
SharedNodePointer& matchingNode = it->second;
matchingNode->setPublicSocket(publicSocket);
matchingNode->setLocalSocket(localSocket);
matchingNode->setPermissions(permissions);
matchingNode->setConnectionSecret(connectionSecret);
matchingNode->setIsReplicated(isReplicated);
matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType));
return matchingNode;
} else {
// we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket);
newNode->setIsReplicated(isReplicated);
newNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType));
newNode->setConnectionSecret(connectionSecret);
newNode->setPermissions(permissions);
// move the newly constructed node to the LNL thread
newNode->moveToThread(thread());
if (nodeType == NodeType::AudioMixer) {
LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer);
}
SharedNodePointer newNodePointer(newNode, &QObject::deleteLater);
// if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node
if (SOLO_NODE_TYPES.count(newNode->getType())) {
// while we still have the read lock, see if there is a previous solo node we'll need to remove
auto previousSoloIt = std::find_if(_nodeHash.cbegin(), _nodeHash.cend(), [newNode](const UUIDNodePair& nodePair){
return nodePair.second->getType() == newNode->getType();
});
if (previousSoloIt != _nodeHash.cend()) {
// we have a previous solo node, switch to a write lock so we can remove it
readLocker.unlock();
QWriteLocker writeLocker(&_nodeMutex);
auto oldSoloNode = previousSoloIt->second;
_nodeHash.unsafe_erase(previousSoloIt);
handleNodeKill(oldSoloNode);
// convert the current lock back to a read lock for insertion of new node
writeLocker.unlock();
readLocker.relock();
}
}
// insert the new node and release our read lock
#if defined(Q_OS_ANDROID) || (defined(__clang__) && defined(Q_OS_LINUX))
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));
#else
_nodeHash.emplace(newNode->getUUID(), newNodePointer);
#endif
readLocker.unlock();
qCDebug(networking) << "Added" << *newNode;
auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambdas to hold a strong ref
emit nodeAdded(newNodePointer);
if (newNodePointer->getActiveSocket()) {
emit nodeActivated(newNodePointer);
} else {
connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [this, weakPtr] {
auto sharedPtr = weakPtr.lock();
if (sharedPtr) {
emit nodeActivated(sharedPtr);
disconnect(sharedPtr.data(), &NetworkPeer::socketActivated, this, 0);
}
});
}
// Signal when a socket changes, so we can start the hole punch over.
connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [this, weakPtr] {
emit nodeSocketUpdated(weakPtr);
});
return newNodePointer;
}
}
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(PingType_t pingType) {
int packetSize = sizeof(PingType_t) + sizeof(quint64);
auto pingPacket = NLPacket::create(PacketType::Ping, packetSize);
pingPacket->writePrimitive(pingType);
pingPacket->writePrimitive(usecTimestampNow());
return pingPacket;
}
std::unique_ptr<NLPacket> LimitedNodeList::constructPingReplyPacket(ReceivedMessage& message) {
PingType_t typeFromOriginalPing;
quint64 timeFromOriginalPing;
message.readPrimitive(&typeFromOriginalPing);
message.readPrimitive(&timeFromOriginalPing);
int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(quint64);
auto replyPacket = NLPacket::create(PacketType::PingReply, packetSize);
replyPacket->writePrimitive(typeFromOriginalPing);
replyPacket->writePrimitive(timeFromOriginalPing);
replyPacket->writePrimitive(usecTimestampNow());
return replyPacket;
}
std::unique_ptr<NLPacket> LimitedNodeList::constructICEPingPacket(PingType_t pingType, const QUuid& iceID) {
int packetSize = NUM_BYTES_RFC4122_UUID + sizeof(PingType_t);
auto icePingPacket = NLPacket::create(PacketType::ICEPing, packetSize);
icePingPacket->write(iceID.toRfc4122());
icePingPacket->writePrimitive(pingType);
return icePingPacket;
}
std::unique_ptr<NLPacket> LimitedNodeList::constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID) {
// pull out the ping type so we can reply back with that
PingType_t pingType;
memcpy(&pingType, message.getRawMessage() + NUM_BYTES_RFC4122_UUID, sizeof(PingType_t));
int packetSize = NUM_BYTES_RFC4122_UUID + sizeof(PingType_t);
auto icePingReplyPacket = NLPacket::create(PacketType::ICEPingReply, packetSize);
// pack the ICE ID and then the ping type
icePingReplyPacket->write(iceID.toRfc4122());
icePingReplyPacket->writePrimitive(pingType);
return icePingReplyPacket;
}
unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes) {
unsigned int n = 0;
eachNode([&](const SharedNodePointer& node){
if (node && destinationNodeTypes.contains(node->getType())) {
sendUnreliablePacket(*packet, *node);
++n;
}
});
return n;
}
SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) {
return nodeMatchingPredicate([&](const SharedNodePointer& node){
return node->getType() == nodeType;
});
}
void LimitedNodeList::getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond) {
packetsInPerSecond = (float) getPacketReceiver().getInPacketCount() / ((float) _packetStatTimer.elapsed() / 1000.0f);
bytesInPerSecond = (float) getPacketReceiver().getInByteCount() / ((float) _packetStatTimer.elapsed() / 1000.0f);
packetsOutPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f);
bytesOutPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f);
}
void LimitedNodeList::resetPacketStats() {
getPacketReceiver().resetCounters();
_numCollectedPackets = 0;
_numCollectedBytes = 0;
_packetStatTimer.restart();
}
void LimitedNodeList::removeSilentNodes() {
QSet<SharedNodePointer> killedNodes;
eachNodeHashIterator([&](NodeHash::iterator& it){
SharedNodePointer node = it->second;
node->getMutex().lock();
if (!node->isForcedNeverSilent()
&& (usecTimestampNow() - node->getLastHeardMicrostamp()) > (NODE_SILENCE_THRESHOLD_MSECS * USECS_PER_MSEC)) {
// call the NodeHash erase to get rid of this node
it = _nodeHash.unsafe_erase(it);
killedNodes.insert(node);
} else {
// we didn't erase this node, push the iterator forwards
++it;
}
node->getMutex().unlock();
});
foreach(const SharedNodePointer& killedNode, killedNodes) {
handleNodeKill(killedNode);
}
}
const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442;
const int NUM_BYTES_STUN_HEADER = 20;
void LimitedNodeList::makeSTUNRequestPacket(char* stunRequestPacket) {
int packetIndex = 0;
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
// leading zeros + message type
const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001);
memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE));
packetIndex += sizeof(REQUEST_MESSAGE_TYPE);
// message length (no additional attributes are included)
uint16_t messageLength = 0;
memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength));
packetIndex += sizeof(messageLength);
memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER));
packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
// transaction ID (random 12-byte unsigned integer)
const uint NUM_TRANSACTION_ID_BYTES = 12;
QUuid randomUUID = QUuid::createUuid();
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
}
void LimitedNodeList::sendSTUNRequest() {
if (!_stunSockAddr.getAddress().isNull()) {
const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10;
if (!_hasCompletedInitialSTUN) {
qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME;
if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) {
// we're still trying to do our initial STUN we're over the fail threshold
stopInitialSTUNUpdate(false);
}
++_numInitialSTUNRequests;
}
char stunRequestPacket[NUM_BYTES_STUN_HEADER];
makeSTUNRequestPacket(stunRequestPacket);
flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest);
_nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
}
}
bool LimitedNodeList::parseSTUNResponse(udt::BasePacket* packet,
QHostAddress& newPublicAddress, uint16_t& newPublicPort) {
// check the cookie to make sure this is actually a STUN response
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS
const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4;
const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020);
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
int attributeStartIndex = NUM_BYTES_STUN_HEADER;
if (memcmp(packet->getData() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH,
&RFC_5389_MAGIC_COOKIE_NETWORK_ORDER,
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) != 0) {
return false;
}
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
while (attributeStartIndex < packet->getDataSize()) {
if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
const int NUM_BYTES_FAMILY_ALIGN = 1;
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
uint8_t addressFamily = 0;
memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily));
byteIndex += sizeof(addressFamily);
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
// grab the X-Port
uint16_t xorMappedPort = 0;
memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort));
newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
byteIndex += sizeof(xorMappedPort);
// grab the X-Address
uint32_t xorMappedAddress = 0;
memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress));
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
// QHostAddress newPublicAddress(stunAddress);
newPublicAddress = QHostAddress(stunAddress);
return true;
}
} else {
// push forward attributeStartIndex by the length of this attribute
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
uint16_t attributeLength = 0;
memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
sizeof(attributeLength));
attributeLength = ntohs(attributeLength);
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
}
}
return false;
}
void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
uint16_t newPublicPort;
QHostAddress newPublicAddress;
if (parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) {
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
qCDebug(networking, "New public socket received from STUN server is %s:%hu",
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
_publicSockAddr.getPort());
if (!_hasCompletedInitialSTUN) {
// if we're here we have definitely completed our initial STUN sequence
stopInitialSTUNUpdate(true);
}
emit publicSockAddrChanged(_publicSockAddr);
flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN);
}
}
}
void LimitedNodeList::startSTUNPublicSocketUpdate() {
if (!_initialSTUNTimer ) {
// setup our initial STUN timer here so we can quickly find out our public IP address
_initialSTUNTimer = new QTimer { this };
connect(_initialSTUNTimer.data(), &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest);
const int STUN_INITIAL_UPDATE_INTERVAL_MSECS = 250;
_initialSTUNTimer->setInterval(STUN_INITIAL_UPDATE_INTERVAL_MSECS); // 250ms, Qt::CoarseTimer acceptable
// if we don't know the STUN IP yet we need to wait until it is known to start STUN requests
if (_stunSockAddr.getAddress().isNull()) {
// if we fail to lookup the socket then timeout the STUN address lookup
connect(&_stunSockAddr, &HifiSockAddr::lookupFailed, this, &LimitedNodeList::possiblyTimeoutSTUNAddressLookup);
// immediately send a STUN request once we know the socket
connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::sendSTUNRequest);
// start the initial STUN timer once we know the socket
connect(&_stunSockAddr, SIGNAL(lookupCompleted()), _initialSTUNTimer, SLOT(start()));
// in case we just completely fail to lookup the stun socket - add a 10s single shot that will trigger the fail case
const quint64 STUN_DNS_LOOKUP_TIMEOUT_MSECS = 10 * 1000;
QTimer* lookupTimeoutTimer = new QTimer { this };
lookupTimeoutTimer->setSingleShot(true);
connect(lookupTimeoutTimer, &QTimer::timeout, this, &LimitedNodeList::possiblyTimeoutSTUNAddressLookup);
// delete the lookup timeout timer once it has fired
connect(lookupTimeoutTimer, &QTimer::timeout, lookupTimeoutTimer, &QTimer::deleteLater);
lookupTimeoutTimer->start(STUN_DNS_LOOKUP_TIMEOUT_MSECS);
} else {
_initialSTUNTimer->start();
// send an initial STUN request right away
sendSTUNRequest();
}
}
}
void LimitedNodeList::possiblyTimeoutSTUNAddressLookup() {
if (_stunSockAddr.getAddress().isNull()) {
// our stun address is still NULL, but we've been waiting for long enough - time to force a fail
stopInitialSTUNUpdate(false);
}
}
void LimitedNodeList::addSTUNHandlerToUnfiltered() {
// make ourselves the handler of STUN packets when they come in
_nodeSocket.addUnfilteredHandler(_stunSockAddr, [this](std::unique_ptr<udt::BasePacket> packet) { processSTUNResponse(std::move(packet)); });
}
void LimitedNodeList::stopInitialSTUNUpdate(bool success) {
_hasCompletedInitialSTUN = true;
if (!success) {
// if we're here this was the last failed STUN request
// use our DS as our stun server
qCDebug(networking, "Failed to lookup public address via STUN server at %s:%hu.",
STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
qCDebug(networking) << "LimitedNodeList public socket will be set with local port and null QHostAddress.";
// reset the public address and port to a null address
_publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort());
// we have changed the publicSockAddr, so emit our signal
emit publicSockAddrChanged(_publicSockAddr);
flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN);
}
// stop our initial fast timer
if (_initialSTUNTimer) {
_initialSTUNTimer->stop();
_initialSTUNTimer->deleteLater();
}
// We now setup a timer here to fire every so often to check that our IP address has not changed.
// Or, if we failed - if will check if we can eventually get a public socket
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
QTimer* stunOccasionalTimer = new QTimer { this };
connect(stunOccasionalTimer, &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest);
stunOccasionalTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
}
void LimitedNodeList::updateLocalSocket() {
// when update is called, if the local socket is empty then start with the guessed local socket
if (_localSockAddr.isNull()) {
setLocalSocket(HifiSockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
}
// attempt to use Google's DNS to confirm that local IP
static const QHostAddress RELIABLE_LOCAL_IP_CHECK_HOST = QHostAddress { "8.8.8.8" };
static const int RELIABLE_LOCAL_IP_CHECK_PORT = 53;
QTcpSocket* localIPTestSocket = new QTcpSocket;
connect(localIPTestSocket, &QTcpSocket::connected, this, &LimitedNodeList::connectedForLocalSocketTest);
connect(localIPTestSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(errorTestingLocalSocket()));
// attempt to connect to our reliable host
localIPTestSocket->connectToHost(RELIABLE_LOCAL_IP_CHECK_HOST, RELIABLE_LOCAL_IP_CHECK_PORT);
}
void LimitedNodeList::connectedForLocalSocketTest() {
auto localIPTestSocket = qobject_cast<QTcpSocket*>(sender());
if (localIPTestSocket) {
auto localHostAddress = localIPTestSocket->localAddress();
if (localHostAddress.protocol() == QAbstractSocket::IPv4Protocol) {
_hasTCPCheckedLocalSocket = true;
setLocalSocket(HifiSockAddr { localHostAddress, _nodeSocket.localPort() });
}
localIPTestSocket->deleteLater();
}
}
void LimitedNodeList::errorTestingLocalSocket() {
auto localIPTestSocket = qobject_cast<QTcpSocket*>(sender());
if (localIPTestSocket) {
// error connecting to the test socket - if we've never set our local socket using this test socket
// then use our possibly updated guessed local address as fallback
if (!_hasTCPCheckedLocalSocket) {
setLocalSocket(HifiSockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
}
localIPTestSocket->deleteLater();;
}
}
void LimitedNodeList::setLocalSocket(const HifiSockAddr& sockAddr) {
if (sockAddr != _localSockAddr) {
if (_localSockAddr.isNull()) {
qCInfo(networking) << "Local socket is" << sockAddr;
} else {
qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr;
}
_localSockAddr = sockAddr;
emit localSockAddrChanged(_localSockAddr);
}
}
void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID,
const QUuid& peerID) {
sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID);
}
SharedNodePointer LimitedNodeList::findNodeWithAddr(const HifiSockAddr& addr) {
QReadLocker locker(&_nodeMutex);
auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&](const UUIDNodePair& pair) {
return pair.second->getActiveSocket() ? (*pair.second->getActiveSocket() == addr) : false;
});
return (it != std::end(_nodeHash)) ? it->second : SharedNodePointer();
}
void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
const QUuid& clientID, const QUuid& peerID) {
auto icePacket = NLPacket::create(packetType);
QDataStream iceDataStream(icePacket.get());
iceDataStream << clientID << _publicSockAddr << _localSockAddr;
if (packetType == PacketType::ICEServerQuery) {
assert(!peerID.isNull());
iceDataStream << peerID;
qCDebug(networking) << "Sending packet to ICE server to request connection info for peer with ID"
<< uuidStringWithoutCurlyBraces(peerID);
}
sendPacket(std::move(icePacket), iceServerSockAddr);
}
void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort) {
// save our local port to shared memory so that assignment client children know how to talk to this parent
QSharedMemory* sharedPortMem = new QSharedMemory(key, parent);
// attempt to create the shared memory segment
if (sharedPortMem->create(sizeof(localPort)) || sharedPortMem->attach()) {
sharedPortMem->lock();
memcpy(sharedPortMem->data(), &localPort, sizeof(localPort));
sharedPortMem->unlock();
qCDebug(networking) << "Wrote local listening port" << localPort << "to shared memory at key" << key;
} else {
qWarning() << "Failed to create and attach to shared memory to share local port with assignment-client children.";
}
}
bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) {
QSharedMemory sharedMem(key);
if (!sharedMem.attach(QSharedMemory::ReadOnly)) {
qCWarning(networking) << "Could not attach to shared memory at key" << key;
return false;
} else {
sharedMem.lock();
memcpy(&localPort, sharedMem.data(), sizeof(localPort));
sharedMem.unlock();
return true;
}
}
void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep) {
QMetaObject::invokeMethod(this, "flagTimeForConnectionStep",
Q_ARG(ConnectionStep, connectionStep),
Q_ARG(quint64, usecTimestampNow()));
}
void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp) {
if (connectionStep == ConnectionStep::LookupAddress) {
QWriteLocker writeLock(&_connectionTimeLock);
// we clear the current times if the user just fired off a lookup
_lastConnectionTimes.clear();
_areConnectionTimesComplete = false;
_lastConnectionTimes[timestamp] = connectionStep;
} else if (!_areConnectionTimesComplete) {
QWriteLocker writeLock(&_connectionTimeLock);
// anything > than sending the first DS check should not come before the DS check in, so we drop those
// this handles the case where you lookup an address and get packets in the existing domain before changing domains
if (connectionStep > LimitedNodeList::ConnectionStep::SendDSCheckIn
&& (_lastConnectionTimes.key(ConnectionStep::SendDSCheckIn) == 0
|| timestamp <= _lastConnectionTimes.key(ConnectionStep::SendDSCheckIn))) {
return;
}
// if there is no time for existing step add a timestamp on the first call for each ConnectionStep
_lastConnectionTimes[timestamp] = connectionStep;
// if this is a received audio packet we consider our connection times complete
if (connectionStep == ConnectionStep::ReceiveFirstAudioPacket) {
_areConnectionTimesComplete = true;
}
}
}
void LimitedNodeList::clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr) {
// for certain reliable channels higher level classes may need to know if the udt::Connection has been reset
auto matchingNode = findNodeWithAddr(sockAddr);
if (matchingNode) {
emit clientConnectionToNodeReset(matchingNode);
}
}
#if (PR_BUILD || DEV_BUILD)
void LimitedNodeList::sendFakedHandshakeRequestToNode(SharedNodePointer node) {
if (node && node->getActiveSocket()) {
_nodeSocket.sendFakedHandshakeRequest(*node->getActiveSocket());
}
}
#endif