1438 lines · 57.2 KB · C++
RawDownload
////  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.