diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 61cc775e08..9d5ee75818 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -56,10 +56,24 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetVersion(); + + QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr()); + + QByteArray myProtocolVersion = protocolVersionsSignature(); + if (nodeConnection.protocolVersion != myProtocolVersion) { + QString protocolVersionError = "Protocol version mismatch - Domain version:" + QCoreApplication::applicationVersion(); + qDebug() << "Protocol Version mismatch - denying connection."; + sendConnectionDeniedPacket(protocolVersionError, message->getSenderSockAddr(), + DomainHandler::ConnectionRefusedReason::ProtocolMismatch); + return; + } + //qDebug() << __FUNCTION__ << "Protocol Version MATCH - continue with processing connection."; + if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; @@ -97,7 +111,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSenderSockAddr(); @@ -332,7 +351,7 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node bool DomainGatekeeper::verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr) { - + // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -370,7 +389,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, } else { if (!senderSockAddr.isNull()) { qDebug() << "Error decrypting username signature for " << username << "- denying connection."; - sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr); + sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } // free up the public key, we don't need it anymore @@ -382,13 +402,15 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, // we can't let this user in since we couldn't convert their public key to an RSA key we could use if (!senderSockAddr.isNull()) { qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; - sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr); + sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } } else { if (!senderSockAddr.isNull()) { qDebug() << "Insufficient data to decrypt username signature - denying connection."; - sendConnectionDeniedPacket("Insufficient data", senderSockAddr); + sendConnectionDeniedPacket("Insufficient data", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } @@ -402,7 +424,8 @@ bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByt if (username.isEmpty()) { qDebug() << "Connect request denied - no username provided."; - sendConnectionDeniedPacket("No username provided", senderSockAddr); + sendConnectionDeniedPacket("No username provided", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); return false; } @@ -416,7 +439,8 @@ bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByt } } else { qDebug() << "Connect request denied for user" << username << "- not in allowed users list."; - sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr); + sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::NotAuthorized); return false; } @@ -452,7 +476,8 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA // deny connection from this user qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; - sendConnectionDeniedPacket("Too many connected users.", senderSockAddr); + sendConnectionDeniedPacket("Too many connected users.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::TooManyUsers); return false; } @@ -516,7 +541,8 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { } } -void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr) { +void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index c4ac32fabf..09e3b04ed7 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -19,6 +19,8 @@ #include #include +#include + #include #include #include @@ -74,7 +76,8 @@ private: const HifiSockAddr& senderSockAddr); void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); - void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr); + void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown); void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index cfec72a24b..18ca7e2941 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -303,6 +303,36 @@ const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; + + +bool DomainServer::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); + PacketVersion headerVersion = NLPacket::versionInHeader(packet); + + //qDebug() << __FUNCTION__ << "type:" << headerType << "version:" << (int)headerVersion; + + auto nodeList = DependencyManager::get(); + + // This implements a special case that handles OLD clients which don't know how to negotiate matching + // protocol versions. We know these clients will sent DomainConnectRequest with older versions. We also + // know these clients will show a warning dialog if they get an EntityData with a protocol version they + // don't understand, so we can send them an empty EntityData with our latest version and they will + // warn the user that the protocol is not compatible + if (headerType == PacketType::DomainConnectRequest && + headerVersion < static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { + + //qDebug() << __FUNCTION__ << "OLD VERSION checkin sending an intentional bad packet -------------------------------"; + + auto packetWithBadVersion = NLPacket::create(PacketType::EntityData); + nodeList->sendPacket(std::move(packetWithBadVersion), packet.getSenderSockAddr()); + return false; + } + + // let the normal nodeList implementation handle all other packets. + return nodeList->isPacketVerified(packet); +} + + void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; @@ -376,6 +406,10 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); + + // set packetVersionMatch as the verify packet operator for the udt::Socket + //using std::placeholders::_1; + nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; @@ -666,6 +700,8 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { + + //qDebug() << __FUNCTION__ << "---------------"; QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); @@ -746,6 +782,9 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) { } void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) { + + //qDebug() << __FUNCTION__ << "---------------"; + const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2; // setup the extended header for the domain list packets diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index fef3221b7d..c39e405380 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -99,6 +99,8 @@ private: void optionallyGetTemporaryName(const QStringList& arguments); + static bool packetVersionMatch(const udt::Packet& packet); + bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 28f769298c..5ddcbf1792 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -19,6 +19,15 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c if (isConnectRequest) { dataStream >> newHeader.connectUUID; + + // Read out the protocol version signature from the connect message + char* rawBytes; + uint length; + + // FIXME -- do we need to delete the rawBytes after it's been copied into the QByteArray? + dataStream.readBytes(rawBytes, length); + newHeader.protocolVersion = QByteArray(rawBytes, length); + //qDebug() << __FUNCTION__ << "...got protocol version from node... version:" << newHeader.protocolVersion; } dataStream >> newHeader.nodeType diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 34119ffdab..9264db637e 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -28,6 +28,8 @@ public: HifiSockAddr senderSockAddr; QList interestList; QString placeName; + + QByteArray protocolVersion; }; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b0e4880011..821cd83c31 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -631,6 +631,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + + connect(&domainHandler, &DomainHandler::resetting, nodeList.data(), &NodeList::resetDomainServerCheckInVersion); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC; @@ -653,7 +655,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); - connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); + + connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, this, &Application::limitOfSilentDomainCheckInsReached); + //connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); + + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // connect to appropriate slots on AccountManager @@ -4569,6 +4575,21 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const { Physics::setSessionUUID(sessionUUID); } + +// If we're not getting anything back from the domain server checkin, it might be that the domain speaks an +// older version of the DomainConnectRequest protocal. We will attempt to send and older version of DomainConnectRequest. +// We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we +// need to be downgraded to talk to it). +void Application::limitOfSilentDomainCheckInsReached() { + //qDebug() << __FUNCTION__; + + auto nodeList = DependencyManager::get(); + + nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version + + nodeList->reset(); +} + bool Application::askToSetAvatarUrl(const QString& url) { QUrl realUrl(url); if (realUrl.isLocalFile()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 28dbcead47..edd1b6187d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -261,6 +261,12 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker() const; +#if (PR_BUILD || DEV_BUILD) + void sendWrongProtocolVersionsSignature(bool checked) { + ::sendWrongProtocolVersionsSignature(checked); + } +#endif + #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); void calibrateEyeTracker1Point(); @@ -314,6 +320,8 @@ private slots: bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; void setSessionUUID(const QUuid& sessionUUID) const; + void limitOfSilentDomainCheckInsReached(); + void domainChanged(const QString& domainHostname); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 538410a47d..a21aa71753 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -545,6 +545,13 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); + #if (PR_BUILD || DEV_BUILD) + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, + qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); + #endif + + + // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 36d285e2cf..fcaf8e6caa 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -167,6 +167,7 @@ namespace MenuOption { const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 0f51a484c4..145d17faaf 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -16,6 +16,8 @@ #include #include +#include + class WebWindowClass; class WindowScriptingInterface : public QObject, public Dependency { @@ -45,7 +47,7 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reason); + void domainConnectionRefused(const QString& reasonMessage, DomainHandler::ConnectionRefusedReason reason = DomainHandler::ConnectionRefusedReason::Unknown); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 44ce63e6c6..08810010a6 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -355,34 +355,53 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes } } +bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { + switch (reasonCode) { + case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::NotAuthorized: + return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; + } + return false; +} + void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { // Read deny reason from packet + ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown; quint16 reasonSize; message->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + QString reasonMessage = QString::fromUtf8(message->readWithoutCopy(reasonSize)); // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in - qCWarning(networking) << "The domain-server denied a connection request: " << reason; + qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; qCWarning(networking) << "Make sure you are logged in."; - if (!_domainConnectionRefusals.contains(reason)) { - _domainConnectionRefusals.append(reason); - emit domainConnectionRefused(reason); + if (!_domainConnectionRefusals.contains(reasonMessage)) { + _domainConnectionRefusals.append(reasonMessage); + emit domainConnectionRefused(reasonMessage, reasonCode); } auto accountManager = DependencyManager::get(); - if (!_hasCheckedForAccessToken) { - accountManager->checkAndSignalForAccessToken(); - _hasCheckedForAccessToken = true; - } + // Some connection refusal reasons imply that a login is required. If so, suggest a new login + if (reasonSuggestsLogin(reasonCode)) { + if (!_hasCheckedForAccessToken) { + accountManager->checkAndSignalForAccessToken(); + _hasCheckedForAccessToken = true; + } - static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; - // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts - if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { - accountManager->generateNewUserKeypair(); - _connectionDenialsSinceKeypairRegen = 0; + // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts + if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { + accountManager->generateNewUserKeypair(); + _connectionDenialsSinceKeypairRegen = 0; + } } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 03141d8fef..4d8505e549 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -84,6 +84,15 @@ public: bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); } void softReset(); + + enum class ConnectionRefusedReason : uint8_t { + Unknown, + ProtocolMismatch, + LoginError, + NotAuthorized, + TooManyUsers + }; + public slots: void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); @@ -115,9 +124,10 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); - void domainConnectionRefused(QString reason); + void domainConnectionRefused(QString reasonMessage, ConnectionRefusedReason reason = ConnectionRefusedReason::Unknown); private: + bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); void sendDisconnectPacket(); void hardReset(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2c10d0627e..714b69fd89 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -162,13 +162,17 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { } bool LimitedNodeList::isPacketVerified(const udt::Packet& packet) { + //qDebug() << __FUNCTION__; return packetVersionMatch(packet) && packetSourceAndHashMatch(packet); } bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); + //qDebug() << __FUNCTION__ << "headerType:" << headerType << "version:" << (int)headerVersion; + if (headerVersion != versionForPacketType(headerType)) { static QMultiHash sourcedVersionDebugSuppressMap; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 0cbe9668b3..3b648a138b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -221,6 +221,10 @@ public: void setConnectionMaxBandwidth(int maxBandwidth) { _nodeSocket.setConnectionMaxBandwidth(maxBandwidth); } + void setPacketFilterOperator(udt::PacketFilterOperator filterOperator) { _nodeSocket.setPacketFilterOperator(filterOperator); } + bool packetVersionMatch(const udt::Packet& packet); + bool isPacketVerified(const udt::Packet& packet); + public slots: void reset(); void eraseAllNodes(); @@ -267,8 +271,6 @@ protected: void setLocalSocket(const HifiSockAddr& sockAddr); - bool isPacketVerified(const udt::Packet& packet); - bool packetVersionMatch(const udt::Packet& packet); bool packetSourceAndHashMatch(const udt::Packet& packet); void processSTUNResponse(std::unique_ptr packet); diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 575a2c7a9c..93f8659663 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -24,8 +24,8 @@ int NLPacket::maxPayloadSize(PacketType type, bool isPartOfMessage) { return Packet::maxPayloadSize(isPartOfMessage) - NLPacket::localHeaderSize(type); } -std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) { - auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage)); +std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) { + auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage, version)); packet->open(QIODevice::ReadWrite); @@ -61,13 +61,12 @@ std::unique_ptr NLPacket::createCopy(const NLPacket& other) { return std::unique_ptr(new NLPacket(other)); } -NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) : +NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) : Packet((size == -1) ? -1 : NLPacket::localHeaderSize(type) + size, isReliable, isPartOfMessage), _type(type), - _version(versionForPacketType(type)) + _version((version == 0) ? versionForPacketType(type) : version) { adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type)); - writeTypeAndVersion(); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index 4527094322..f49f8498a5 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -38,7 +38,7 @@ public: sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; static std::unique_ptr create(PacketType type, qint64 size = -1, - bool isReliable = false, bool isPartOfMessage = false); + bool isReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); @@ -73,7 +73,7 @@ public: protected: - NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false); + NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); NLPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); NLPacket(const NLPacket& other); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index c295ffc700..50677908a5 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -292,7 +292,9 @@ void NodeList::sendDomainServerCheckIn() { return; } - auto domainPacket = NLPacket::create(domainPacketType); + auto packetVersion = (domainPacketType == PacketType::DomainConnectRequest) ? _domainConnectRequestVersion : 0; + //qDebug() << __FUNCTION__ << " NLPacket::create() version:" << (int)packetVersion; + auto domainPacket = NLPacket::create(domainPacketType, -1, false, false, packetVersion); QDataStream packetStream(domainPacket.get()); @@ -312,12 +314,28 @@ void NodeList::sendDomainServerCheckIn() { // pack the connect UUID for this connect request packetStream << connectUUID; + + // include the protocol version signature in our connect request + if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { + QByteArray protocolVersionSig = protocolVersionsSignature(); + packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size()); + //qDebug() << __FUNCTION__ << " including protocol version --------------------------"; + } else { + //qDebug() << __FUNCTION__ << "_domainConnectRequestVersion less than HasProtocolVersions - not including protocol version"; + } + } else { + //qDebug() << __FUNCTION__ << "NOT a DomainConnnectRequest ----------- not including checkin details -------"; } // 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 << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList() - << DependencyManager::get()->getPlaceName(); + packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); + if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasHostname)) { + packetStream << DependencyManager::get()->getPlaceName(); + //qDebug() << __FUNCTION__ << " including host name --------------------------"; + } else { + //qDebug() << __FUNCTION__ << "_domainConnectRequestVersion less than HasHostname - not including host name"; + } if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); @@ -345,6 +363,7 @@ void NodeList::sendDomainServerCheckIn() { // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; + //qDebug() << __FUNCTION__ << " _numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns << " --------------------------"; } } @@ -504,15 +523,22 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { + //qDebug() << __FUNCTION__; + if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS return; } + //qDebug() << __FUNCTION__ << "_numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns; + // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; + //qDebug() << __FUNCTION__ << "RESET.... _numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns; + // emit our signal so listeners know we just heard from the DS + //qDebug() << __FUNCTION__ << "about to emit receivedDomainServerList() -----------------------------------------------"; emit receivedDomainServerList(); DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 4b196d5f7b..3158262c87 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -68,6 +68,13 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } + /// downgrades the DomainConnnectRequest PacketVersion to attempt to probe for older domain servers + void downgradeDomainServerCheckInVersion() { + qDebug() << __FUNCTION__ << "----------------------------------------------------------"; + _domainConnectRequestVersion--; + + } + public slots: void reset(); void sendDomainServerCheckIn(); @@ -85,6 +92,12 @@ public slots: void processICEPingPacket(QSharedPointer message); + void resetDomainServerCheckInVersion() + { + qDebug() << __FUNCTION__ << "----------------------------------------------------------"; + _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); + } + signals: void limitOfSilentDomainCheckInsReached(); void receivedDomainServerList(); @@ -123,6 +136,8 @@ private: HifiSockAddr _assignmentServerSocket; bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; + + PacketVersion _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); }; #endif // hifi_NodeList_h diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 530efc5fb3..cb47625d6d 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -309,22 +309,29 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); - + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); - + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + } else { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage)); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } } else { listenerIsDead = true; } } else { + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "Got verified unsourced packet list." << "packetType:" << packetType; + // qDebug() << "Got verified unsourced packet list: " << QString(nlPacketList->getMessage()); emit dataReceived(NodeType::Unassigned, receivedMessage->getSize()); @@ -332,6 +339,8 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei if (listener.object) { success = listener.method.invoke(listener.object, Q_ARG(QSharedPointer, receivedMessage)); + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } else { listenerIsDead = true; } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b04d582d6d..72875322f6 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -12,7 +12,9 @@ #include "PacketHeaders.h" #include +#include +#include #include #include @@ -58,9 +60,13 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetUpload: // Removal of extension from Asset requests return 18; + + case PacketType::DomainConnectionDenied: + return static_cast(DomainConnectionDeniedVersion::IncludesReasonCode); + case PacketType::DomainConnectRequest: - // addition of referring hostname information - return 18; + return static_cast(DomainConnectRequestVersion::HasProtocolVersions); + default: return 17; } @@ -80,3 +86,36 @@ QDebug operator<<(QDebug debug, const PacketType& type) { debug.nospace().noquote() << (uint8_t) type << " (" << typeName << ")"; return debug.space(); } + +#if (PR_BUILD || DEV_BUILD) +static bool sendWrongProtocolVersion = false; +void sendWrongProtocolVersionsSignature(bool sendWrongVersion) { + sendWrongProtocolVersion = sendWrongVersion; +} +#endif + +QByteArray protocolVersionsSignature() { + static QByteArray protocolVersionSignature; + static std::once_flag once; + std::call_once(once, [&] { + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + uint8_t numberOfProtocols = static_cast(PacketType::LAST_PACKET_TYPE) + 1; + stream << numberOfProtocols; + for (uint8_t packetType = 0; packetType < numberOfProtocols; packetType++) { + uint8_t packetTypeVersion = static_cast(versionForPacketType(static_cast(packetType))); + stream << packetTypeVersion; + } + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(buffer); + protocolVersionSignature = hash.result(); + }); + + #if (PR_BUILD || DEV_BUILD) + if (sendWrongProtocolVersion) { + return QByteArray("INCORRECTVERSION"); // only for debugging version checking + } + #endif + + return protocolVersionSignature; +} diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 030b4af8c9..b6237e74d6 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -61,7 +61,7 @@ public: AssignmentClientStatus, NoisyMute, AvatarIdentity, - AvatarBillboard, + TYPE_UNUSED_1, DomainConnectRequest, DomainServerRequireDTLS, NodeJsonStats, @@ -94,7 +94,8 @@ public: ICEServerHeartbeatDenied, AssetMappingOperation, AssetMappingOperationReply, - ICEServerHeartbeatACK + ICEServerHeartbeatACK, + LAST_PACKET_TYPE = ICEServerHeartbeatACK }; }; @@ -109,6 +110,11 @@ extern const QSet NON_SOURCED_PACKETS; extern const QSet RELIABLE_PACKETS; PacketVersion versionForPacketType(PacketType packetType); +QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols + +#if (PR_BUILD || DEV_BUILD) +void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation +#endif uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); @@ -179,4 +185,15 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarEntities }; +enum class DomainConnectRequestVersion : PacketVersion { + NoHostname = 17, + HasHostname, + HasProtocolVersions +}; + +enum class DomainConnectionDeniedVersion : PacketVersion { + ReasonMessageOnly = 17, + IncludesReasonCode +}; + #endif // hifi_PacketHeaders_h