//// 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. Load more
// // 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);