1416 lines · 62.7 KB · C++
RawDownload
////  NodeList.cpp//  libraries/networking/src////  Created by Stephen Birarda on 2/15/13.//  Copyright 2013 High Fidelity, Inc.//  Copyright 2020 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 "NodeList.h" #include <chrono> #include <QtCore/QDataStream>#include <QtCore/QDebug>#include <QtCore/QJsonDocument>#include <QtCore/QMetaEnum>#include <QtCore/QUrl>#include <QtCore/QThread>#include <QtNetwork/QHostInfo>#include <QtNetwork/QNetworkInterface> #include <shared/QtHelpers.h>#include <ThreadHelpers.h>#include <LogHandler.h>#include <UUID.h>#include <platform/Platform.h>#include <platform/PlatformKeys.h> #include "AccountManager.h"#include "AddressManager.h"#include "Assignment.h"#include "AudioHelpers.h"#include "DomainAccountManager.h"#include "SockAddr.h"#include "FingerprintUtils.h" #include "NetworkLogging.h"#include "udt/PacketHeaders.h"#include "SharedUtil.h"#include <Trace.h>#include <ModerationFlags.h>#include "WarningsSuppression.h" using namespace std::chrono; const int KEEPALIVE_PING_INTERVAL_MS = 1000;const int MAX_SYSTEM_INFO_SIZE = 1000; NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) :    LimitedNodeList(socketListenPort, dtlsListenPort),    _ownerType(newOwnerType),    _nodeTypesOfInterest(),    _domainHandler(this),    _assignmentServerSocket(),    _keepAlivePingTimer(this){    setCustomDeleter([](Dependency* dependency){        static_cast<NodeList*>(dependency)->deleteLater();    });     auto addressManager = DependencyManager::get<AddressManager>();     // handle domain change signals from AddressManager    connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,            &_domainHandler, &DomainHandler::setURLAndID);     connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,            &_domainHandler, &DomainHandler::setIceServerHostnameAndID);     // handle a request for a path change from the AddressManager    connect(addressManager.data(), &AddressManager::pathChangeRequired, this, &NodeList::handleDSPathQuery);     // in case we don't know how to talk to DS when a path change is requested    // fire off any pending DS path query when we get socket information    connect(&_domainHandler, &DomainHandler::connectedToDomain, this, &NodeList::sendPendingDSPathQuery);     // send a domain server check in immediately once the DS socket is known    connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendDomainServerCheckIn);     // send a domain server check in immediately if there is a public socket change    connect(this, &LimitedNodeList::publicSockAddrChanged, this, &NodeList::sendDomainServerCheckIn);     // clear our NodeList when the domain changes    connect(&_domainHandler, SIGNAL(disconnectedFromDomain()), this, SLOT(resetFromDomainHandler()));     // send an ICE heartbeat as soon as we get ice server information    connect(&_domainHandler, &DomainHandler::iceSocketAndIDReceived, this, &NodeList::handleICEConnectionToDomainServer);     // handle ping timeout from DomainHandler to establish a connection with auto networked domain-server    connect(&_domainHandler.getICEPeer(), &NetworkPeer::pingTimerTimeout, this, &NodeList::pingPunchForDomainServer);     // send a ping punch immediately    connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);     // FIXME: Can remove this temporary work-around in version 2021.2.0. (New protocol version implies a domain server upgrade.)    // Adjust our canRezAvatarEntities permissions on older domains that do not have this setting.    // DomainServerList and DomainSettings packets can come in either order so need to adjust with both occurrences.    connect(&_domainHandler, &DomainHandler::settingsReceived, this, &NodeList::adjustCanRezAvatarEntitiesPerSettings);     auto accountManager = DependencyManager::get<AccountManager>();     // assume that we may need to send a new DS check in anytime a new keypair is generated    connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);     // clear out NodeList when login is finished and we know our new username    connect(accountManager.data(), &AccountManager::usernameChanged , this, [this]{ reset("Username changed"); });     // clear our NodeList when logout is requested    connect(accountManager.data(), &AccountManager::logoutComplete , this, [this]{ reset("Logged out"); });     // Only used in Interface.    auto domainAccountManager = DependencyManager::get<DomainAccountManager>();    if (domainAccountManager) {        _hasDomainAccountManager = true;        connect(domainAccountManager.data(), &DomainAccountManager::newTokens, this, &NodeList::sendDomainServerCheckIn);    }     // anytime we get a new node we will want to attempt to punch to it    connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);    connect(this, &LimitedNodeList::nodeSocketUpdated, this, &NodeList::startNodeHolePunch);     // anytime we get a new node we may need to re-send our set of ignored node IDs to it    connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode);     // setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)    _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable    connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings);    connect(&_domainHandler, SIGNAL(connectedToDomain(QUrl)), &_keepAlivePingTimer, SLOT(start()));    connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop);     connect(&_domainHandler, &DomainHandler::limitOfSilentDomainCheckInsReached, this, [this]() {        if (_connectReason != Awake) {            _connectReason = SilentDomainDisconnect;        }    });     // set our sockAddrBelongsToDomainOrNode method as the connection creation filter for the udt::Socket    using std::placeholders::_1;    _nodeSocket.setConnectionCreationFilterOperator(std::bind(&NodeList::sockAddrBelongsToDomainOrNode, this, _1));     // we definitely want STUN to update our public socket, so call the LNL to kick that off    startSTUNPublicSocketUpdate();     auto& packetReceiver = getPacketReceiver();    packetReceiver.registerListener(PacketType::DomainList,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainList));    packetReceiver.registerListener(PacketType::Ping,        PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingPacket));    packetReceiver.registerListener(PacketType::PingReply,        PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingReplyPacket));    packetReceiver.registerListener(PacketType::ICEPing,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processICEPingPacket));    packetReceiver.registerListener(PacketType::DomainServerAddedNode,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerAddedNode));    packetReceiver.registerListener(PacketType::DomainServerConnectionToken,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerConnectionTokenPacket));    packetReceiver.registerListener(PacketType::DomainConnectionDenied,        PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processDomainServerConnectionDeniedPacket));    packetReceiver.registerListener(PacketType::DomainSettings,        PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processSettingsPacketList));    packetReceiver.registerListener(PacketType::ICEServerPeerInformation,        PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processICEResponsePacket));    packetReceiver.registerListener(PacketType::DomainServerRequireDTLS,        PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processDTLSRequirementPacket));    packetReceiver.registerListener(PacketType::ICEPingReply,        PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processICEPingReplyPacket));    packetReceiver.registerListener(PacketType::DomainServerPathResponse,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerPathResponse));    packetReceiver.registerListener(PacketType::DomainServerRemovedNode,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerRemovedNode));    packetReceiver.registerListener(PacketType::UsernameFromIDReply,        PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processUsernameFromIDReply));} qint64 NodeList::sendStats(QJsonObject statsObject, SockAddr destination) {    if (thread() != QThread::currentThread()) {        QMetaObject::invokeMethod(this, "sendStats", Qt::QueuedConnection,                                  Q_ARG(QJsonObject, statsObject),                                  Q_ARG(SockAddr, destination));        return 0;    }     auto statsPacketList = NLPacketList::create(PacketType::NodeJsonStats, QByteArray(), true, true);     QJsonDocument jsonDocument(statsObject);     statsPacketList->write(QCborValue::fromJsonValue(jsonDocument.object()).toCbor());     sendPacketList(std::move(statsPacketList), destination);    return 0;} qint64 NodeList::sendStatsToDomainServer(QJsonObject statsObject) {    if (thread() != QThread::currentThread()) {        QMetaObject::invokeMethod(this, "sendStatsToDomainServer", Qt::QueuedConnection,                                  Q_ARG(QJsonObject, statsObject));        return 0;    }     return sendStats(statsObject, _domainHandler.getSockAddr());} void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode) {    PingType_t pingType;     quint64 ourOriginalTime, othersReplyTime;     message.seek(0);     message.readPrimitive(&pingType);    message.readPrimitive(&ourOriginalTime);    message.readPrimitive(&othersReplyTime);     quint64 now = usecTimestampNow();    qint64 pingTime = now - ourOriginalTime;    qint64 oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight     // The other node's expected time should be our original time plus the one way flight time    // anything other than that is clock skew    quint64 othersExpectedReply = ourOriginalTime + oneWayFlightTime;    qint64 clockSkew = othersReplyTime - othersExpectedReply;     sendingNode->setPingMs(pingTime / 1000);    sendingNode->updateClockSkewUsec(clockSkew);     const bool wantDebug = false;     if (wantDebug) {        auto averageClockSkew = sendingNode->getClockSkewUsec();        qCDebug(networking) << "PING_REPLY from node " << *sendingNode << "\n" <<        "                     now: " << now << "\n" <<        "                 ourTime: " << ourOriginalTime << "\n" <<        "                pingTime: " << pingTime << "\n" <<        "        oneWayFlightTime: " << oneWayFlightTime << "\n" <<        "         othersReplyTime: " << othersReplyTime << "\n" <<        "    othersExprectedReply: " << othersExpectedReply << "\n" <<        "               clockSkew: " << clockSkew << "[" << formatUsecTime(clockSkew) << "]" << "\n" <<        "       average clockSkew: " << averageClockSkew << "[" << formatUsecTime(averageClockSkew) << "]";    }} void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {    // send back a reply    auto replyPacket = constructPingReplyPacket(*message);    const SockAddr& senderSockAddr = message->getSenderSockAddr();    sendPacket(std::move(replyPacket), *sendingNode, senderSockAddr);     // If we don't have a symmetric socket for this node and this socket doesn't match    // what we have for public and local then set it as the symmetric.    // This allows a server on a reachable port to communicate with nodes on symmetric NATs    if (sendingNode->getSymmetricSocket().isNull()) {        if (senderSockAddr != sendingNode->getLocalSocket() && senderSockAddr != sendingNode->getPublicSocket()) {            sendingNode->setSymmetricSocket(senderSockAddr);        }    }     int64_t connectionID;     message->readPrimitive(&connectionID);     auto it = _connectionIDs.find(sendingNode->getUUID());    if (it != _connectionIDs.end()) {        if (connectionID > it->second) {            qDebug() << "Received a ping packet with a larger connection id (" << connectionID << ">" << it->second << ") from "                     << sendingNode->getUUID();            killNodeWithUUID(sendingNode->getUUID(), connectionID);        }    } } void NodeList::processPingReplyPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {    // activate the appropriate socket for this node, if not yet updated    activateSocketFromNodeCommunication(*message, sendingNode);     // set the ping time for this node for stat collection    timePingReply(*message, sendingNode);} void NodeList::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {    // send back a reply    auto replyPacket = constructICEPingReplyPacket(*message, _domainHandler.getICEClientID());    sendPacket(std::move(replyPacket), message->getSenderSockAddr());} void NodeList::reset(QString reason, bool skipDomainHandlerReset) {    if (thread() != QThread::currentThread()) {        QMetaObject::invokeMethod(this, "reset",                                  Q_ARG(QString, reason),                                  Q_ARG(bool, skipDomainHandlerReset));        return;    }    LimitedNodeList::reset(reason);     // lock and clear our set of ignored IDs    _ignoredSetLock.lockForWrite();    _ignoredNodeIDs.clear();    _ignoredSetLock.unlock();    // lock and clear our set of personally muted IDs    _personalMutedSetLock.lockForWrite();    _personalMutedNodeIDs.clear();    _personalMutedSetLock.unlock();     // lock and clear out set of avatarGains    _avatarGainMapLock.lockForWrite();    _avatarGainMap.clear();    _avatarGainMapLock.unlock();     if (!skipDomainHandlerReset) {        // clear the domain connection information, unless they're the ones that asked us to reset        _domainHandler.softReset(reason);    }     // refresh the owner UUID to the NULL UUID    setSessionUUID(QUuid());    setSessionLocalID(Node::NULL_LOCAL_ID);     // if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking    if (_dtlsSocket) {        disconnect(_dtlsSocket, 0, this, 0);    }} void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) {    _nodeTypesOfInterest << nodeTypeToAdd;} void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) {    _nodeTypesOfInterest.unite(setOfNodeTypes);} void NodeList::sendDomainServerCheckIn() {     // On ThreadedAssignments (assignment clients), this function    // is called by the server check-in timer thread    // not the NodeList thread.  Calling it on the NodeList thread    // resulted in starvation of the server check-in function.    // be VERY CAREFUL modifying this code as members of NodeList    // may be called by multiple threads.     if (!_sendDomainServerCheckInEnabled) {        static const QString DISABLED_CHECKIN_DEBUG{ "Refusing to send a domain-server check in while it is disabled." };        HIFI_FCDEBUG(networking_ice(), DISABLED_CHECKIN_DEBUG);        return;    }     if (_isShuttingDown) {        qCDebug(networking_ice) << "Refusing to send a domain-server check in while shutting down.";        return;    }     auto publicSockAddr = _publicSockAddr;    auto domainHandlerIp = _domainHandler.getIP();     if (publicSockAddr.isNull()) {        // we don't know our public socket and we need to send it to the domain server        qCDebug(networking_ice) << "Waiting for initial public socket from STUN. Will not send domain-server check in.";    } else if (domainHandlerIp.isNull() && _domainHandler.requiresICE()) {        qCDebug(networking_ice) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";        handleICEConnectionToDomainServer();        // let the domain handler know we are due to send a checkin packet    } else if (!domainHandlerIp.isNull() && !_domainHandler.checkInPacketTimeout()) {        bool domainIsConnected = _domainHandler.isConnected();        SockAddr domainSockAddr = _domainHandler.getSockAddr();        PacketType domainPacketType = !domainIsConnected            ? PacketType::DomainConnectRequest : PacketType::DomainListRequest;         if (!domainIsConnected) {            auto hostname = _domainHandler.getHostname();            QMetaEnum metaEnum = QMetaEnum::fromType<LimitedNodeList::ConnectReason>();            qCDebug(networking_ice) << "Sending connect request ( REASON:" << QString(metaEnum.valueToKey(_connectReason)) << ") to domain-server at" << hostname;             // is this our localhost domain-server?            // if so we need to make sure we have an up-to-date local port in case it restarted             if ((domainSockAddr.getAddress() == QHostAddress::LocalHost || hostname == "localhost")                && _domainPortAutoDiscovery) {                 quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;                getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);                qCDebug(networking_ice) << "Local domain-server port read from shared memory (or default) is" << domainPort;                _domainHandler.setPort(domainPort);            }        }         // check if we're missing a keypair we need to verify ourselves with the domain-server        auto accountManager = DependencyManager::get<AccountManager>();        const QUuid& connectionToken = _domainHandler.getConnectionToken();         bool requiresUsernameSignature = !domainIsConnected && !connectionToken.isNull();         if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {            qCWarning(networking_ice) << "A keypair is required to present a username signature to the domain-server"                << "but no keypair is present. Waiting for keypair generation to complete.";            accountManager->generateNewUserKeypair();             // don't send the check in packet - wait for the new public key to be available to the domain-server first            return;        }         // WEBRTC TODO: Move code into packet library. And update reference in DomainConnectRequest.js.         auto domainPacket = NLPacket::create(domainPacketType);         QDataStream packetStream(domainPacket.get());         SockAddr localSockAddr = _localSockAddr;        if (domainPacketType == PacketType::DomainConnectRequest) { #if (PR_BUILD || DEV_BUILD)            if (_shouldSendNewerVersion) {                domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);            }#endif             QUuid connectUUID = _domainHandler.getAssignmentUUID();             if (connectUUID.isNull() && _domainHandler.requiresICE()) {                // this is a connect request and we're an interface client                // that used ice to discover the DS                // so send our ICE client UUID with the connect request                connectUUID = _domainHandler.getICEClientID();            }             // pack the connect UUID for this connect request            packetStream << connectUUID;             // include the protocol version signature in our connect request            QByteArray protocolVersionSig = protocolVersionsSignature();            packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());             // PROTOCOL TODO: remove the system info and mac address fields entirely            packetStream << QString();             // now add the machine fingerprint            packetStream << FingerprintUtils::getMachineFingerprint();             // PROTOCOL TODO: remove the system info and mac address fields entirely            QByteArray compressedSystemInfo = qCompress(QByteArray());             if (compressedSystemInfo.size() > MAX_SYSTEM_INFO_SIZE) {                // FIXME                // Highly unlikely, as not even unreasonable machines will                // overflow the max size, but prevent MTU overflow anyway.                // We could do something sophisticated like clearing specific                // values if they're too big, but we'll save that for later.                // Alternative solution would be to write system info at the end of the packet, only if there is space.                compressedSystemInfo.clear();            }             packetStream << compressedSystemInfo;             packetStream << _connectReason;             if (_nodeDisconnectTimestamp < _nodeConnectTimestamp) {                _nodeDisconnectTimestamp = usecTimestampNow();            }            quint64 previousConnectionUptime = _nodeConnectTimestamp ? _nodeDisconnectTimestamp - _nodeConnectTimestamp : 0;             packetStream << previousConnectionUptime;         }         packetStream << quint64(duration_cast<microseconds>(system_clock::now().time_since_epoch()).count());         // pack our data to send to the domain-server including        // the hostname information (so the domain-server can see which place name we came in on)        packetStream << _ownerType.load() << publicSockAddr.getType() << publicSockAddr << localSockAddr.getType()            << localSockAddr << _nodeTypesOfInterest.values();        packetStream << DependencyManager::get<AddressManager>()->getPlaceName();         if (!domainIsConnected) {             // Directory services account.            DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();            packetStream << accountInfo.getUsername();            // if this is a connect request, and we can present a username signature, send it along            if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) {                const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken);                packetStream << usernameSignature;            } else {                packetStream << QString("");  // Placeholder in case have domain username.            }             // Domain account.            if (_hasDomainAccountManager) {                auto domainAccountManager = DependencyManager::get<DomainAccountManager>();                if (!domainAccountManager->getUsername().isEmpty() && !domainAccountManager->getAccessToken().isEmpty()) {                    packetStream << domainAccountManager->getUsername();                    packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken());                }            }         }         flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);

Lines 1–500 of 1416.