//// LimitedNodeList.cpp// libraries/networking/src//// Created by Stephen Birarda on 2/15/13.// Copyright 2013 High Fidelity, Inc.// Copyright 2021 Vircadia contributors.//// 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 "SockAddr.h"#include "NetworkLogging.h"#include "udt/Packet.h"#include "HMACAuth.h" #if defined(Q_OS_WIN)#include <winsock.h>#else#include <arpa/inet.h>#endif static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); using namespace std::chrono_literals;static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL_MS = 1s; LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : _nodeSocket(this, true), _packetReceiver(new PacketReceiver(this)){ qRegisterMetaType<ConnectionStep>("ConnectionStep"); auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); _nodeSocket.bind(SocketType::UDP, QHostAddress::AnyIPv4, port); quint16 assignedPort = _nodeSocket.localPort(SocketType::UDP); if (socketListenPort != INVALID_PORT && socketListenPort != 0 && socketListenPort != assignedPort) { qCCritical(networking) << "PAGE: NodeList is unable to assign requested UDP port of" << socketListenPort; } qCDebug(networking) << "NodeList UDP socket is listening on" << assignedPort; 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); if (dtlsListenPort != 0 && _dtlsSocket->localPort() != dtlsListenPort) { qCDebug(networking) << "NodeList is unable to assign requested DTLS port of" << dtlsListenPort; } qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); } _nodeSocket.bind(SocketType::WebRTC, QHostAddress::AnyIPv4); // 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); const int CONNECTION_STATS_SAMPLE_INTERVAL_MSECS = 1000; QTimer* statsSampleTimer = new QTimer(this); connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats); statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS); // Flush delayed adds every second QTimer* delayedAddsFlushTimer = new QTimer(this); connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds); delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL_MS.count()); // 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](SockAddr 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); if (_stunSockAddr.getAddress().isNull()) { // we don't know the stun server socket yet, add it to unfiltered once known connect(&_stunSockAddr, &SockAddr::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); }} Node::LocalID LimitedNodeList::getSessionLocalID() const { QReadLocker readLock { &_sessionUUIDLock }; return _sessionLocalID;} void LimitedNodeList::setSessionLocalID(Node::LocalID sessionLocalID) { QWriteLocker lock { &_sessionUUIDLock }; _sessionLocalID = sessionLocalID;} 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::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)); } if (originalPermissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData) != newPermissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData)) { emit canGetAndSetPrivateUserDataChanged(_permissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData)); } if (originalPermissions.can(NodePermissions::Permission::canRezAvatarEntities) != newPermissions.can(NodePermissions::Permission::canRezAvatarEntities)) { emit canRezAvatarEntitiesChanged(_permissions.can(NodePermissions::Permission::canRezAvatarEntities)); } if (originalPermissions.can(NodePermissions::Permission::canViewAssetURLs) != newPermissions.can(NodePermissions::Permission::canViewAssetURLs)) { emit canViewAssetURLsChanged(_permissions.can(NodePermissions::Permission::canViewAssetURLs)); }} void LimitedNodeList::setSocketLocalPort(SocketType socketType, quint16 socketLocalPort) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setSocketLocalPort", Qt::QueuedConnection, Q_ARG(quint16, socketLocalPort)); return; } if (_nodeSocket.localPort(socketType) != socketLocalPort) { _nodeSocket.rebind(socketType, socketLocalPort); if (socketType == SocketType::UDP) { LIMITED_NODELIST_LOCAL_PORT.set(socketLocalPort); } else { // WEBRTC TODO: Add WebRTC equivalent? qCWarning(networking_webrtc) << "LIMITED_NODELIST_LOCAL_PORT not set for WebRTC socket"; } }} 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;} #if defined(WEBRTC_DATA_CHANNELS)const WebRTCSocket* LimitedNodeList::getWebRTCSocket() { return _nodeSocket.getWebRTCSocket();}#endif 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<SockAddr, PacketType> versionDebugSuppressMap; bool hasBeenOutput = false; QString senderString; const SockAddr& 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 { SharedNodePointer sourceNode = nodeWithLocalID(NLPacket::sourceIDInHeader(packet)); if (sourceNode) { sourceID = sourceNode->getUUID(); 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) { return true; } else { HIFI_FCDEBUG(networking(), "Replicated packet of type" << headerType << "received from unknown upstream" << packet.getSenderSockAddr()); return false; } } else { return true; } } else { NLPacket::LocalID sourceLocalID = Node::NULL_LOCAL_ID; // 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 sourceLocalID = NLPacket::sourceIDInHeader(packet); SharedNodePointer matchingNode = nodeWithLocalID(sourceLocalID); sourceNode = matchingNode.data(); } QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && !isDomainServer() && sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { // This is a packet sourced by the domain server return true; } if (sourceNode) { bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType); bool verificationEnabled = !(isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType)) && _useAuthentication; if (verifiedPacket && verificationEnabled) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash; auto sourceNodeHMACAuth = sourceNode->getAuthenticateHash(); if (sourceNode->getAuthenticateHash()) { expectedHash = NLPacket::hashForPacketAndHMAC(packet, *sourceNodeHMACAuth); } // check if the HMAC-md5 hash in the header matches the hash we would expect if (!sourceNodeHMACAuth || packetHeaderHash != expectedHash) { static QMultiMap<QUuid, PacketType> hashDebugSuppressMap; if (!hashDebugSuppressMap.contains(sourceID, headerType)) { qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID; qCDebug(networking) << "Packet len:" << packet.getDataSize() << "Expected hash:" << expectedHash.toHex() << "Actual:" << packetHeaderHash.toHex(); 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()); return true; } else if (!isDelayedNode(sourceID)){ HIFI_FCDEBUG(networking(), "Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID); } } return false;} void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) { if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) { packet.writeSourceID(getSessionLocalID()); } if (_useAuthentication && hmacAuth && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { packet.writeVerificationHash(*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; } return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getAuthenticateHash());} qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const SockAddr& sockAddr, HMACAuth* hmacAuth) { Q_ASSERT(!packet.isPartOfMessage()); Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", "Trying to send a reliable packet unreliably."); if (_dropOutgoingNodeTraffic) { auto destinationNode = findNodeWithAddr(sockAddr); // findNodeWithAddr returns null for the address of the domain server if (!destinationNode.isNull()) { // This only suppresses individual unreliable packets, not unreliable packet lists return ERROR_SENDING_PACKET_BYTES; } } 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) { 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 SockAddr& sockAddr, HMACAuth* hmacAuth) { Q_ASSERT(!packet->isPartOfMessage()); if (packet->isReliable()) { fillPacketHeader(*packet, hmacAuth); auto size = packet->getDataSize(); _nodeSocket.writePacket(std::move(packet), sockAddr); return size; } else { auto size = sendUnreliablePacket(*packet, sockAddr, hmacAuth); if (size < 0) { auto now = usecTimestampNow(); if (now - _sendErrorStatsTime > ERROR_STATS_PERIOD_US) { _sendErrorStatsTime = now; eachNode([now](const SharedNodePointer& node) { qCDebug(networking) << "Stats for " << node->getPublicSocket() << "\n" << " Last Heard Microstamp: " << node->getLastHeardMicrostamp() << " (" << (now - node->getLastHeardMicrostamp()) << "usec ago)\n" << " Outbound Kbps: " << node->getOutboundKbps() << "\n" << " Inbound Kbps: " << node->getInboundKbps() << "\n" << " Ping: " << node->getPingMs(); }); } } return size; }} 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);Lines 1–500 of 1438. Load more
// // LimitedNodeList.cpp // libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. // // 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 "SockAddr.h" #include "NetworkLogging.h" #include "udt/Packet.h" #include "HMACAuth.h" #if defined(Q_OS_WIN) #include <winsock.h> #else #include <arpa/inet.h> #endif static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); using namespace std::chrono_literals; static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL_MS = 1s; LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : _nodeSocket(this, true), _packetReceiver(new PacketReceiver(this)) { qRegisterMetaType<ConnectionStep>("ConnectionStep"); auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); _nodeSocket.bind(SocketType::UDP, QHostAddress::AnyIPv4, port); quint16 assignedPort = _nodeSocket.localPort(SocketType::UDP); if (socketListenPort != INVALID_PORT && socketListenPort != 0 && socketListenPort != assignedPort) { qCCritical(networking) << "PAGE: NodeList is unable to assign requested UDP port of" << socketListenPort; } qCDebug(networking) << "NodeList UDP socket is listening on" << assignedPort; 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); if (dtlsListenPort != 0 && _dtlsSocket->localPort() != dtlsListenPort) { qCDebug(networking) << "NodeList is unable to assign requested DTLS port of" << dtlsListenPort; } qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); } _nodeSocket.bind(SocketType::WebRTC, QHostAddress::AnyIPv4); // 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); const int CONNECTION_STATS_SAMPLE_INTERVAL_MSECS = 1000; QTimer* statsSampleTimer = new QTimer(this); connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats); statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS); // Flush delayed adds every second QTimer* delayedAddsFlushTimer = new QTimer(this); connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds); delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL_MS.count()); // 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](SockAddr 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); if (_stunSockAddr.getAddress().isNull()) { // we don't know the stun server socket yet, add it to unfiltered once known connect(&_stunSockAddr, &SockAddr::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); } } Node::LocalID LimitedNodeList::getSessionLocalID() const { QReadLocker readLock { &_sessionUUIDLock }; return _sessionLocalID; } void LimitedNodeList::setSessionLocalID(Node::LocalID sessionLocalID) { QWriteLocker lock { &_sessionUUIDLock }; _sessionLocalID = sessionLocalID; } 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::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)); } if (originalPermissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData) != newPermissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData)) { emit canGetAndSetPrivateUserDataChanged(_permissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData)); } if (originalPermissions.can(NodePermissions::Permission::canRezAvatarEntities) != newPermissions.can(NodePermissions::Permission::canRezAvatarEntities)) { emit canRezAvatarEntitiesChanged(_permissions.can(NodePermissions::Permission::canRezAvatarEntities)); } if (originalPermissions.can(NodePermissions::Permission::canViewAssetURLs) != newPermissions.can(NodePermissions::Permission::canViewAssetURLs)) { emit canViewAssetURLsChanged(_permissions.can(NodePermissions::Permission::canViewAssetURLs)); } } void LimitedNodeList::setSocketLocalPort(SocketType socketType, quint16 socketLocalPort) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setSocketLocalPort", Qt::QueuedConnection, Q_ARG(quint16, socketLocalPort)); return; } if (_nodeSocket.localPort(socketType) != socketLocalPort) { _nodeSocket.rebind(socketType, socketLocalPort); if (socketType == SocketType::UDP) { LIMITED_NODELIST_LOCAL_PORT.set(socketLocalPort); } else { // WEBRTC TODO: Add WebRTC equivalent? qCWarning(networking_webrtc) << "LIMITED_NODELIST_LOCAL_PORT not set for WebRTC socket"; } } } 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; } #if defined(WEBRTC_DATA_CHANNELS) const WebRTCSocket* LimitedNodeList::getWebRTCSocket() { return _nodeSocket.getWebRTCSocket(); } #endif 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<SockAddr, PacketType> versionDebugSuppressMap; bool hasBeenOutput = false; QString senderString; const SockAddr& 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 { SharedNodePointer sourceNode = nodeWithLocalID(NLPacket::sourceIDInHeader(packet)); if (sourceNode) { sourceID = sourceNode->getUUID(); 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) { return true; } else { HIFI_FCDEBUG(networking(), "Replicated packet of type" << headerType << "received from unknown upstream" << packet.getSenderSockAddr()); return false; } } else { return true; } } else { NLPacket::LocalID sourceLocalID = Node::NULL_LOCAL_ID; // 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 sourceLocalID = NLPacket::sourceIDInHeader(packet); SharedNodePointer matchingNode = nodeWithLocalID(sourceLocalID); sourceNode = matchingNode.data(); } QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && !isDomainServer() && sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { // This is a packet sourced by the domain server return true; } if (sourceNode) { bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType); bool verificationEnabled = !(isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType)) && _useAuthentication; if (verifiedPacket && verificationEnabled) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash; auto sourceNodeHMACAuth = sourceNode->getAuthenticateHash(); if (sourceNode->getAuthenticateHash()) { expectedHash = NLPacket::hashForPacketAndHMAC(packet, *sourceNodeHMACAuth); } // check if the HMAC-md5 hash in the header matches the hash we would expect if (!sourceNodeHMACAuth || packetHeaderHash != expectedHash) { static QMultiMap<QUuid, PacketType> hashDebugSuppressMap; if (!hashDebugSuppressMap.contains(sourceID, headerType)) { qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID; qCDebug(networking) << "Packet len:" << packet.getDataSize() << "Expected hash:" << expectedHash.toHex() << "Actual:" << packetHeaderHash.toHex(); 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()); return true; } else if (!isDelayedNode(sourceID)){ HIFI_FCDEBUG(networking(), "Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID); } } return false; } void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) { if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) { packet.writeSourceID(getSessionLocalID()); } if (_useAuthentication && hmacAuth && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { packet.writeVerificationHash(*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; } return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getAuthenticateHash()); } qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const SockAddr& sockAddr, HMACAuth* hmacAuth) { Q_ASSERT(!packet.isPartOfMessage()); Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", "Trying to send a reliable packet unreliably."); if (_dropOutgoingNodeTraffic) { auto destinationNode = findNodeWithAddr(sockAddr); // findNodeWithAddr returns null for the address of the domain server if (!destinationNode.isNull()) { // This only suppresses individual unreliable packets, not unreliable packet lists return ERROR_SENDING_PACKET_BYTES; } } 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) { 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 SockAddr& sockAddr, HMACAuth* hmacAuth) { Q_ASSERT(!packet->isPartOfMessage()); if (packet->isReliable()) { fillPacketHeader(*packet, hmacAuth); auto size = packet->getDataSize(); _nodeSocket.writePacket(std::move(packet), sockAddr); return size; } else { auto size = sendUnreliablePacket(*packet, sockAddr, hmacAuth); if (size < 0) { auto now = usecTimestampNow(); if (now - _sendErrorStatsTime > ERROR_STATS_PERIOD_US) { _sendErrorStatsTime = now; eachNode([now](const SharedNodePointer& node) { qCDebug(networking) << "Stats for " << node->getPublicSocket() << "\n" << " Last Heard Microstamp: " << node->getLastHeardMicrostamp() << " (" << (now - node->getLastHeardMicrostamp()) << "usec ago)\n" << " Outbound Kbps: " << node->getOutboundKbps() << "\n" << " Inbound Kbps: " << node->getInboundKbps() << "\n" << " Ping: " << node->getPingMs(); }); } } return size; } } 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);