mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-29 14:02:59 +02:00
1240 lines
49 KiB
C++
1240 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"
|
|
|
|
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->getConnectionSecret());
|
|
|
|
// 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, const QUuid& connectionSecret) {
|
|
if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) {
|
|
packet.writeSourceID(getSessionUUID());
|
|
}
|
|
|
|
if (!connectionSecret.isNull()
|
|
&& !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())
|
|
&& !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) {
|
|
packet.writeVerificationHashGivenSecret(connectionSecret);
|
|
}
|
|
}
|
|
|
|
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.getConnectionSecret());
|
|
}
|
|
|
|
qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
|
|
const QUuid& connectionSecret) {
|
|
Q_ASSERT(!packet.isPartOfMessage());
|
|
Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket",
|
|
"Trying to send a reliable packet unreliably.");
|
|
|
|
collectPacketStats(packet);
|
|
fillPacketHeader(packet, connectionSecret);
|
|
|
|
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.getConnectionSecret());
|
|
} 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,
|
|
const QUuid& connectionSecret) {
|
|
Q_ASSERT(!packet->isPartOfMessage());
|
|
if (packet->isReliable()) {
|
|
collectPacketStats(*packet);
|
|
fillPacketHeader(*packet, connectionSecret);
|
|
|
|
auto size = packet->getDataSize();
|
|
_nodeSocket.writePacket(std::move(packet), sockAddr);
|
|
|
|
return size;
|
|
} else {
|
|
return sendUnreliablePacket(*packet, sockAddr, connectionSecret);
|
|
}
|
|
}
|
|
|
|
qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode) {
|
|
auto activeSocket = destinationNode.getActiveSocket();
|
|
|
|
if (activeSocket) {
|
|
qint64 bytesSent = 0;
|
|
auto connectionSecret = destinationNode.getConnectionSecret();
|
|
|
|
// close the last packet in the list
|
|
packetList.closeCurrentPacket();
|
|
|
|
while (!packetList._packets.empty()) {
|
|
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket, connectionSecret);
|
|
}
|
|
|
|
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,
|
|
const QUuid& connectionSecret) {
|
|
qint64 bytesSent = 0;
|
|
|
|
// close the last packet in the list
|
|
packetList.closeCurrentPacket();
|
|
|
|
while (!packetList._packets.empty()) {
|
|
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, connectionSecret);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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.getConnectionSecret());
|
|
}
|
|
|
|
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.getConnectionSecret());
|
|
}
|
|
|
|
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
|