From e8772277de2a54e92fccbe81002b9c67a371283a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 14:19:33 -0700 Subject: [PATCH 01/11] de-bloat the domain-server by adding a DomainGatekeeper --- domain-server/src/DomainGatekeeper.cpp | 589 ++++++++++++++++++ domain-server/src/DomainGatekeeper.h | 93 +++ domain-server/src/DomainServer.cpp | 567 ++--------------- domain-server/src/DomainServer.h | 39 +- domain-server/src/NodeConnectionData.cpp | 37 ++ domain-server/src/NodeConnectionData.h | 32 + domain-server/src/PendingAssignedNodeData.cpp | 6 +- domain-server/src/PendingAssignedNodeData.h | 2 +- libraries/networking/src/LimitedNodeList.h | 2 +- libraries/networking/src/UUIDHasher.h | 12 +- libraries/octree/src/OctreeEditPacketSender.h | 8 - 11 files changed, 816 insertions(+), 571 deletions(-) create mode 100644 domain-server/src/DomainGatekeeper.cpp create mode 100644 domain-server/src/DomainGatekeeper.h create mode 100644 domain-server/src/NodeConnectionData.cpp create mode 100644 domain-server/src/NodeConnectionData.h diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp new file mode 100644 index 0000000000..f712229e2d --- /dev/null +++ b/domain-server/src/DomainGatekeeper.cpp @@ -0,0 +1,589 @@ +// +// DomainGatekeeper.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2015-08-24. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DomainGatekeeper.h" + +#include +#include +#include + +#include +#include +#include + +#include "DomainServer.h" +#include "DomainServerNodeData.h" + +using SharedAssignmentPointer = QSharedPointer; + +DomainGatekeeper::DomainGatekeeper(DomainServer* server) : + _server(server) +{ + +} + +void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID, + const QUuid& walletUUID, const QString& nodeVersion) { + _pendingAssignedNodes.emplace(std::piecewise_construct, + std::forward_as_tuple(nodeUUID), + std::forward_as_tuple(assignmentUUID, walletUUID, nodeVersion)); +} + +QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) { + auto it = _pendingAssignedNodes.find(tempUUID); + + if (it != _pendingAssignedNodes.end()) { + return it->second.getAssignmentUUID(); + } else { + return QUuid(); + } +} + +const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer +<< NodeType::AvatarMixer << NodeType::EntityServer; + +void DomainGatekeeper::processConnectRequestPacket(QSharedPointer packet) { + if (packet->getPayloadSize() == 0) { + return; + } + + QDataStream packetStream(packet.data()); + + // read a NodePacketHeader from the packet so we can pass around this data while we're inspecting it + NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr()); + + if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { + qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; + return; + } + + // check if this connect request matches an assignment in the queue + auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID); + + SharedNodePointer node; + + if (pendingAssignment != _pendingAssignedNodes.end()) { + node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second); + } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { + QString username; + QByteArray usernameSignature; + + if (packet->bytesLeftToRead() > 0) { + // read username from packet + packetStream >> username; + + if (packet->bytesLeftToRead() > 0) { + // read user signature from packet + packetStream >> usernameSignature; + } + } + + node = processAgentConnectRequest(nodeConnection, username, usernameSignature); + } + + if (node) { + // set the sending sock addr and node interest set on this node + DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + nodeData->setSendingSockAddr(packet->getSenderSockAddr()); + nodeData->setNodeInterestSet(nodeConnection.interestList.toSet()); + + // signal that we just connected a node so the DomainServer can get it a list + // and broadcast its presence right away + emit connectedNode(node); + } +} + +SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, + const PendingAssignedNodeData& pendingAssignment) { + SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); + auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID); + + if (it != _pendingAssignedNodes.end()) { + matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType); + + if (matchingQueuedAssignment) { + qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID) + << "matches unfulfilled assignment" + << uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID()); + } else { + // this is a node connecting to fulfill an assignment that doesn't exist + // don't reply back to them so they cycle back and re-request an assignment + qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID); + return SharedNodePointer(); + } + } else { + qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID); + return SharedNodePointer(); + } + + SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); + + // when the newNode is created the linked data is also created + // if this was a static assignment set the UUID, set the sendingSockAddr + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + + nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); + nodeData->setWalletUUID(it->second.getWalletUUID()); + nodeData->setNodeVersion(it->second.getNodeVersion()); + + // cleanup the PendingAssignedNodeData for this assignment now that it's connecting + _pendingAssignedNodes.erase(it); + + // always allow assignment clients to create and destroy entities + newNode->setCanAdjustLocks(true); + newNode->setCanRez(true); + + return newNode; +} + +const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; +const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; +const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; + +SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, + const QString& username, + const QByteArray& usernameSignature) { + + auto limitedNodeList = DependencyManager::get(); + + bool isRestrictingAccess = + _server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + + // check if this user is on our local machine - is this is true they are always allowed to connect + QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); + bool isLocalUser = + (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); + + // if we're using restricted access and this user is not local make sure we got a user signature + if (isRestrictingAccess && !isLocalUser) { + if (!username.isEmpty()) { + if (usernameSignature.isEmpty()) { + // if user didn't include usernameSignature in connect request, send a connectionToken packet + sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); + return SharedNodePointer(); + } + } + } + + bool verifiedUsername = false; + + // if we do not have a local user we need to subject them to our verification and capacity checks + if (!isLocalUser) { + + // check if we need to look at the username signature + if (isRestrictingAccess) { + if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) { + // we verified the user via their username and signature - set the verifiedUsername + // so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to + // being in the allowed editors list) + verifiedUsername = true; + } else { + // failed to verify user - return a null shared ptr + return SharedNodePointer(); + } + } + + if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) { + // we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor + // or couldn't be verified as one) + return SharedNodePointer(); + } + } + + SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); + + // if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true + const QVariant* allowedEditorsVariant = + valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); + QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); + + bool isAllowedEditor = allowedEditors.isEmpty() || allowedEditors.contains(username); + + bool canAdjustLocks = false; + + if (isAllowedEditor) { + if (!verifiedUsername) { + if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) { + qDebug() << "Could not verify user" << username << "as allowed editor. User will still be allowed to connect" + << "but will not have edit privileges."; + + canAdjustLocks = true; + } else { + canAdjustLocks = true; + } + } else { + canAdjustLocks = true; + } + } + + const QVariant* editorsAreRezzersVariant = + valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); + + bool onlyEditorsAreRezzers = false; + if (editorsAreRezzersVariant) { + onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); + } + + bool canRez = true; + if (onlyEditorsAreRezzers) { + canRez = canAdjustLocks; + } + + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + + // if we have a username from the connect request, set it on the DomainServerNodeData + nodeData->setUsername(username); + + // also add an interpolation to JSONBreakableMarshal so that servers can get username in stats + JSONBreakableMarshal::addInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, + uuidStringWithoutCurlyBraces(newNode->getUUID()), username); + + return newNode; +} + +SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) { + HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr; + SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID); + + QUuid nodeUUID; + + if (connectedPeer) { + // this user negotiated a connection with us via ICE, so re-use their ICE client ID + nodeUUID = nodeConnection.connectUUID; + + if (connectedPeer->getActiveSocket()) { + // set their discovered socket to whatever the activated socket on the network peer object was + discoveredSocket = *connectedPeer->getActiveSocket(); + } + } else { + // we got a connectUUID we didn't recognize, just add the node with a new UUID + nodeUUID = QUuid::createUuid(); + } + + auto limitedNodeList = DependencyManager::get(); + + SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeConnection.nodeType, + nodeConnection.publicSockAddr, nodeConnection.localSockAddr); + + // So that we can send messages to this node at will - we need to activate the correct socket on this node now + newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket); + + return newNode; +} + +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); + + const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); + + if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { + // if we do have a public key for the user, check for a signature match + + const unsigned char* publicKeyData = reinterpret_cast(publicKeyArray.constData()); + + // first load up the public key into an RSA struct + RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); + + QByteArray lowercaseUsername = username.toLower().toUtf8(); + QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), + QCryptographicHash::Sha256); + + if (rsaPublicKey) { + QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); + int decryptResult = RSA_verify(NID_sha256, + reinterpret_cast(usernameWithToken.constData()), + usernameWithToken.size(), + reinterpret_cast(usernameSignature.constData()), + usernameSignature.size(), + rsaPublicKey); + + if (decryptResult == 1) { + qDebug() << "Username signature matches for" << username << "- allowing connection."; + + // free up the public key and remove connection token before we return + RSA_free(rsaPublicKey); + _connectionTokenHash.remove(username); + + return true; + + } else { + if (!senderSockAddr.isNull()) { + qDebug() << "Error decrypting username signature for " << username << "- denying connection."; + sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr); + } + + // free up the public key, we don't need it anymore + RSA_free(rsaPublicKey); + } + + } else { + + // 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); + } + } + } else { + if (!senderSockAddr.isNull()) { + qDebug() << "Insufficient data to decrypt username signature - denying connection."; + sendConnectionDeniedPacket("Insufficient data", senderSockAddr); + } + } + + requestUserPublicKey(username); // no joy. maybe next time? + return false; +} + +bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, + const HifiSockAddr& senderSockAddr) { + + QStringList allowedUsers = + _server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); + + if (allowedUsers.contains(username, Qt::CaseInsensitive)) { + if (username.isEmpty()) { + qDebug() << "Connect request denied - no username provided."; + + sendConnectionDeniedPacket("No username provided", senderSockAddr); + + return false; + } + if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) { + return false; + } + } else { + qDebug() << "Connect request denied for user" << username << "- not in allowed users list."; + sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr); + + return false; + } + + return true; +} + +bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature, + bool& verifiedUsername, + const HifiSockAddr& senderSockAddr) { + // find out what our maximum capacity is + const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); + unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; + + if (maximumUserCapacity > 0) { + unsigned int connectedUsers = _server->countConnectedUsers(); + + if (connectedUsers >= maximumUserCapacity) { + // too many users, deny the new connection unless this user is an allowed editor + + const QVariant* allowedEditorsVariant = + valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); + + QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); + if (allowedEditors.contains(username)) { + if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) { + verifiedUsername = true; + return true; + } + } + + // deny connection from this user + qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; + sendConnectionDeniedPacket("Too many connected users.", senderSockAddr); + + return false; + } + + qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection."; + } + + return true; +} + + +void DomainGatekeeper::preloadAllowedUserPublicKeys() { + const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH); + QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); + + if (allowedUsers.size() > 0) { + // in the future we may need to limit how many requests here - for now assume that lists of allowed users are not + // going to create > 100 requests + foreach(const QString& username, allowedUsers) { + requestUserPublicKey(username); + } + } +} + +void DomainGatekeeper::requestUserPublicKey(const QString& username) { + // even if we have a public key for them right now, request a new one in case it has just changed + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; + + const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; + + qDebug() << "Requesting public key for user" << username; + + AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, callbackParams); +} + +void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + + if (jsonObject["status"].toString() == "success") { + // figure out which user this is for + + const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; + QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); + + if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { + QString username = usernameRegex.cap(1); + + qDebug() << "Storing a public key for user" << username; + + // pull the public key as a QByteArray from this response + const QString JSON_DATA_KEY = "data"; + const QString JSON_PUBLIC_KEY_KEY = "public_key"; + + _userPublicKeys[username] = + QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()); + } + } +} + +void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr) { + // 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(); + + // setup the DomainConnectionDenied packet + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); + + // packet in the reason the connection was denied (the client displays this) + if (payloadSize > 0) { + connectionDeniedPacket->writePrimitive(payloadSize); + connectionDeniedPacket->write(utfString); + } + + // send the packet off + DependencyManager::get()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr); +} + +void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) { + // get the existing connection token or create a new one + QUuid& connectionToken = _connectionTokenHash[username.toLower()]; + + if (connectionToken.isNull()) { + connectionToken = QUuid::createUuid(); + } + + // setup a static connection token packet + static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID); + + // reset the packet before each time we send + connectionTokenPacket->reset(); + + // write the connection token + connectionTokenPacket->write(connectionToken.toRfc4122()); + + // send off the packet unreliably + DependencyManager::get()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr); +} + +const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; + +void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { + + if (peer->getConnectionAttempts() > 0 && peer->getConnectionAttempts() % NUM_PEER_PINGS_BEFORE_DELETE == 0) { + // we've reached the maximum number of ping attempts + qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); + qDebug() << "Removing from list of connecting peers."; + + _icePeers.remove(peer->getUUID()); + } else { + auto limitedNodeList = DependencyManager::get(); + + // send the ping packet to the local and public sockets for this node + auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID()); + limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket()); + + auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID()); + limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket()); + + peer->incrementConnectionAttempts(); + } +} + +void DomainGatekeeper::handlePeerPingTimeout() { + NetworkPeer* senderPeer = qobject_cast(sender()); + + if (senderPeer) { + SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID()); + + if (sharedPeer && !sharedPeer->getActiveSocket()) { + pingPunchForConnectingPeer(sharedPeer); + } + } +} + +void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer packet) { + // loop through the packet and pull out network peers + // any peer we don't have we add to the hash, otherwise we update + QDataStream iceResponseStream(packet.data()); + + NetworkPeer* receivedPeer = new NetworkPeer; + iceResponseStream >> *receivedPeer; + + if (!_icePeers.contains(receivedPeer->getUUID())) { + qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer; + SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer); + _icePeers[receivedPeer->getUUID()] = newPeer; + + // make sure we know when we should ping this peer + connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout); + + // immediately ping the new peer, and start a timer to continue pinging it until we connect to it + newPeer->startPingTimer(); + + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" + << newPeer->getUUID(); + + pingPunchForConnectingPeer(newPeer); + } else { + delete receivedPeer; + } +} + +void DomainGatekeeper::processICEPingPacket(QSharedPointer packet) { + auto limitedNodeList = DependencyManager::get(); + auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*packet, limitedNodeList->getSessionUUID()); + + limitedNodeList->sendPacket(std::move(pingReplyPacket), packet->getSenderSockAddr()); +} + +void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer packet) { + QDataStream packetStream(packet.data()); + + QUuid nodeUUID; + packetStream >> nodeUUID; + + SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID); + + if (sendingPeer) { + // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list + sendingPeer->activateMatchingOrNewSymmetricSocket(packet->getSenderSockAddr()); + } +} diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h new file mode 100644 index 0000000000..857b4419da --- /dev/null +++ b/domain-server/src/DomainGatekeeper.h @@ -0,0 +1,93 @@ +// +// DomainGatekeeper.h +// domain-server/src +// +// Created by Stephen Birarda on 2015-08-24. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_DomainGatekeeper_h +#define hifi_DomainGatekeeper_h + +#include + +#include +#include + +#include +#include +#include + +#include "NodeConnectionData.h" +#include "PendingAssignedNodeData.h" + +class DomainServer; + +class DomainGatekeeper : public QObject { + Q_OBJECT +public: + DomainGatekeeper(DomainServer* server); + + void addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID, + const QUuid& walletUUID, const QString& nodeVersion); + QUuid assignmentUUIDForPendingAssignment(const QUuid& tempUUID); + + void preloadAllowedUserPublicKeys(); + + void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); } +public slots: + void processConnectRequestPacket(QSharedPointer packet); + void processICEPingPacket(QSharedPointer packet); + void processICEPingReplyPacket(QSharedPointer packet); + void processICEPeerInformationPacket(QSharedPointer packet); + + void publicKeyJSONCallback(QNetworkReply& requestReply); + +signals: + void connectedNode(SharedNodePointer node); + +private slots: + void handlePeerPingTimeout(); +private: + SharedNodePointer processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, + const PendingAssignedNodeData& pendingAssignment); + SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection, + const QString& username, + const QByteArray& usernameSignature); + SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection); + + bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, + const HifiSockAddr& senderSockAddr); + bool isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, + const HifiSockAddr& senderSockAddr); + bool isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature, + bool& verifiedUsername, + const HifiSockAddr& senderSockAddr); + + bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, + const HifiSockAddr& senderSockAddr); + + void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); + void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr); + + void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); + + void requestUserPublicKey(const QString& username); + + DomainServer* _server; + + std::unordered_map _pendingAssignedNodes; + + QHash _icePeers; + + QHash _connectionTokenHash; + QHash _userPublicKeys; +}; + + +#endif // hifi_DomainGatekeeper_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 23957380e6..12a9e094aa 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -9,9 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include +#include "DomainServer.h" #include #include @@ -38,24 +36,19 @@ #include #include "DomainServerNodeData.h" - -#include "DomainServer.h" +#include "NodeConnectionData.h" int const DomainServer::EXIT_CODE_REBOOT = 234923; const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io"; -const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; -const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; -const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; - DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), + _gatekeeper(this), _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _httpsManager(NULL), _allAssignments(), _unfulfilledAssignments(), - _pendingAssignedNodes(), _isUsingDTLS(false), _oauthProviderURL(), _oauthClientID(), @@ -94,6 +87,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : qRegisterMetaType("DomainServerWebSessionData"); qRegisterMetaTypeStreamOperators("DomainServerWebSessionData"); + + // make sure we hear about newly connected nodes from our gatekeeper + connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) { // we either read a certificate and private key or were not passed one @@ -108,7 +104,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : setupAutomaticNetworking(); // preload some user public keys so they can connect on first request - preloadAllowedUserPublicKeys(); + _gatekeeper.preloadAllowedUserPublicKeys(); } } @@ -284,13 +280,13 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket"); - packetReceiver.registerListener(PacketType::DomainConnectRequest, this, "processConnectRequestPacket"); + packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); - packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); - packetReceiver.registerListener(PacketType::ICEPingReply, this, "processICEPingReplyPacket"); - packetReceiver.registerListener(PacketType::ICEServerPeerInformation, this, "processICEPeerInformationPacket"); + packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); + packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); + packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); @@ -575,217 +571,20 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet packet) { - NodeType_t nodeType; - HifiSockAddr publicSockAddr, localSockAddr; - - if (packet->getPayloadSize() == 0) { - return; - } - - QDataStream packetStream(packet.data()); - - QUuid connectUUID; - packetStream >> connectUUID; - - const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr(); - - parseNodeData(packetStream, nodeType, publicSockAddr, localSockAddr, senderSockAddr); - - if (localSockAddr.isNull() || senderSockAddr.isNull()) { - qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; - return; - } - - // check if this connect request matches an assignment in the queue - bool isAssignment = _pendingAssignedNodes.contains(connectUUID); - SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); - PendingAssignedNodeData* pendingAssigneeData = NULL; - - if (isAssignment) { - pendingAssigneeData = _pendingAssignedNodes.value(connectUUID); - - if (pendingAssigneeData) { - matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType); - - if (matchingQueuedAssignment) { - qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(connectUUID) - << "matches unfulfilled assignment" - << uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID()); - - // remove this unique assignment deployment from the hash of pending assigned nodes - // cleanup of the PendingAssignedNodeData happens below after the node has been added to the LimitedNodeList - _pendingAssignedNodes.remove(connectUUID); - } else { - // this is a node connecting to fulfill an assignment that doesn't exist - // don't reply back to them so they cycle back and re-request an assignment - qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(connectUUID); - return; - } - } - } - - QList nodeInterestList; - QString username; - QByteArray usernameSignature; - - auto limitedNodeList = DependencyManager::get(); - - packetStream >> nodeInterestList; - - if (packet->bytesLeftToRead() > 0) { - // try to verify username - packetStream >> username; - } - - bool isRestrictingAccess = - _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); - - // we always let in a user who is sending a packet from our local socket or from the localhost address - bool isLocalUser = (senderSockAddr.getAddress() == DependencyManager::get()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost); - - if (isRestrictingAccess && !isLocalUser) { - if (!username.isEmpty()) { - // if there's a username, try to unpack username signature - packetStream >> usernameSignature; - - if (usernameSignature.isEmpty()) { - // if user didn't include usernameSignature in connect request, send a connectionToken packet - QUuid& connectionToken = _connectionTokenHash[username.toLower()]; - - if (connectionToken.isNull()) { - connectionToken = QUuid::createUuid(); - } - - static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID); - connectionTokenPacket->reset(); - connectionTokenPacket->write(connectionToken.toRfc4122()); - limitedNodeList->sendUnreliablePacket(*connectionTokenPacket, packet->getSenderSockAddr()); - return; - } - } - } - - QString reason; - if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr, reason)) { - // 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(); - - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); - if (payloadSize > 0) { - connectionDeniedPacket->writePrimitive(payloadSize); - connectionDeniedPacket->write(utfString); - } - // tell client it has been refused. - limitedNodeList->sendPacket(std::move(connectionDeniedPacket), senderSockAddr); - return; - } - - if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) - || (isAssignment && matchingQueuedAssignment)) { - // this was either not a static assignment or it was and we had a matching one in the queue - - QUuid nodeUUID; - - HifiSockAddr discoveredSocket = senderSockAddr; - SharedNetworkPeer connectedPeer = _icePeers.value(connectUUID); - - if (connectedPeer) { - // this user negotiated a connection with us via ICE, so re-use their ICE client ID - nodeUUID = connectUUID; - - if (connectedPeer->getActiveSocket()) { - // set their discovered socket to whatever the activated socket on the network peer object was - discoveredSocket = *connectedPeer->getActiveSocket(); - } - } else { - // we got a connectUUID we didn't recognize, just add the node with a new UUID - nodeUUID = QUuid::createUuid(); - } - - // if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true - const QVariant* allowedEditorsVariant = - valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); - QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - bool canAdjustLocks = allowedEditors.isEmpty() || allowedEditors.contains(username); - - const QVariant* editorsAreRezzersVariant = - valueForKeyPath(_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); - - bool onlyEditorsAreRezzers = false; - if (editorsAreRezzersVariant) { - onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); - } - - bool canRez = true; - if (onlyEditorsAreRezzers) { - canRez = canAdjustLocks; - } - - SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeType, - publicSockAddr, localSockAddr, - canAdjustLocks, canRez); - - // So that we can send messages to this node at will - we need to activate the correct socket on this node now - newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket); - - // when the newNode is created the linked data is also created - // if this was a static assignment set the UUID, set the sendingSockAddr - DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - - if (isAssignment) { - nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); - nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID()); - nodeData->setNodeVersion(pendingAssigneeData->getNodeVersion()); - - // always allow assignment clients to create and destroy entities - newNode->setCanAdjustLocks(true); - newNode->setCanRez(true); - - // now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data - delete pendingAssigneeData; - } - - if (!username.isEmpty()) { - // if we have a username from the connect request, set it on the DomainServerNodeData - nodeData->setUsername(username); - - // also add an interpolation to JSONBreakableMarshal so that servers can get username in stats - JSONBreakableMarshal::addInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, - uuidStringWithoutCurlyBraces(nodeUUID), username); - } - - nodeData->setSendingSockAddr(senderSockAddr); - - // reply back to the user with a PacketType::DomainList - sendDomainListToNode(newNode, senderSockAddr, nodeInterestList.toSet()); - - // send out this node to our other connected nodes - broadcastNewNode(newNode); - } -} - - void DomainServer::processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - NodeType_t throwawayNodeType; - HifiSockAddr nodePublicAddress, nodeLocalAddress; - QDataStream packetStream(packet.data()); + NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr()); - parseNodeData(packetStream, throwawayNodeType, nodePublicAddress, nodeLocalAddress, packet->getSenderSockAddr()); + // update this node's sockets in case they have changed + sendingNode->setPublicSocket(nodeRequestData.publicSockAddr); + sendingNode->setLocalSocket(nodeRequestData.localSockAddr); + + // update the NodeInterestSet in case there have been any changes + DomainServerNodeData* nodeData = reinterpret_cast(sendingNode->getLinkedData()); + nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet()); - sendingNode->setPublicSocket(nodePublicAddress); - sendingNode->setLocalSocket(nodeLocalAddress); - - QList nodeInterestList; - packetStream >> nodeInterestList; - - sendDomainListToNode(sendingNode, packet->getSenderSockAddr(), nodeInterestList.toSet()); + sendDomainListToNode(sendingNode, packet->getSenderSockAddr()); } unsigned int DomainServer::countConnectedUsers() { @@ -799,156 +598,6 @@ unsigned int DomainServer::countConnectedUsers() { return result; } - -bool DomainServer::verifyUserSignature(const QString& username, - const QByteArray& usernameSignature, - QString& reasonReturn) { - - // it's possible this user can be allowed to connect, but we need to check their username signature - QByteArray publicKeyArray = _userPublicKeys.value(username); - - const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); - - if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { - // if we do have a public key for the user, check for a signature match - - const unsigned char* publicKeyData = reinterpret_cast(publicKeyArray.constData()); - - // first load up the public key into an RSA struct - RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); - - QByteArray lowercaseUsername = username.toLower().toUtf8(); - QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), - QCryptographicHash::Sha256); - - if (rsaPublicKey) { - QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); - int decryptResult = RSA_verify(NID_sha256, - reinterpret_cast(usernameWithToken.constData()), - usernameWithToken.size(), - reinterpret_cast(usernameSignature.constData()), - usernameSignature.size(), - rsaPublicKey); - - if (decryptResult == 1) { - qDebug() << "Username signature matches for" << username << "- allowing connection."; - - // free up the public key and remove connection token before we return - RSA_free(rsaPublicKey); - _connectionTokenHash.remove(username); - - return true; - - } else { - qDebug() << "Error decrypting username signature for " << username << "- denying connection."; - reasonReturn = "Error decrypting username signature."; - // free up the public key, we don't need it anymore - RSA_free(rsaPublicKey); - } - - } else { - - // we can't let this user in since we couldn't convert their public key to an RSA key we could use - qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; - reasonReturn = "Couldn't convert data to RSA key."; - } - } else { - qDebug() << "Insufficient data to decrypt username signature - denying connection."; - reasonReturn = "Insufficient data"; - } - - requestUserPublicKey(username); // no joy. maybe next time? - return false; -} - - -bool DomainServer::shouldAllowConnectionFromNode(const QString& username, - const QByteArray& usernameSignature, - const HifiSockAddr& senderSockAddr, - QString& reasonReturn) { - - //TODO: improve flow so these bools aren't declared twice - bool isRestrictingAccess = - _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); - bool isLocalUser = (senderSockAddr.getAddress() == DependencyManager::get()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost); - - if (isRestrictingAccess && !isLocalUser) { - QStringList allowedUsers = - _settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); - - if (allowedUsers.contains(username, Qt::CaseInsensitive)) { - if (username.isEmpty()) { - qDebug() << "Connect request denied - no username provided."; - reasonReturn = "No username provided"; - return false; - } - if (!verifyUserSignature(username, usernameSignature, reasonReturn)) { - return false; - } - } else { - qDebug() << "Connect request denied for user" << username << "not in allowed users list."; - reasonReturn = "User not on whitelist."; - - return false; - } - } - - // either we aren't restricting users, or this user is in the allowed list - // if this user is in the editors list, exempt them from the max-capacity check - const QVariant* allowedEditorsVariant = - valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); - QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - if (allowedEditors.contains(username)) { - if (verifyUserSignature(username, usernameSignature, reasonReturn)) { - return true; - } - } - - // if we haven't reached max-capacity, let them in. - const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); - unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; - if (maximumUserCapacity > 0) { - unsigned int connectedUsers = countConnectedUsers(); - if (connectedUsers >= maximumUserCapacity) { - // too many users, deny the new connection. - qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; - reasonReturn = "Too many connected users."; - return false; - } - qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection."; - } - - return true; -} - -void DomainServer::preloadAllowedUserPublicKeys() { - const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH); - QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); - - if (allowedUsers.size() > 0) { - // in the future we may need to limit how many requests here - for now assume that lists of allowed users are not - // going to create > 100 requests - foreach(const QString& username, allowedUsers) { - requestUserPublicKey(username); - } - } -} - -void DomainServer::requestUserPublicKey(const QString& username) { - // even if we have a public key for them right now, request a new one in case it has just changed - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; - - const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; - - qDebug() << "Requesting public key for user" << username; - - AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username), - AccountManagerAuth::None, - QNetworkAccessManager::GetOperation, callbackParams); -} - QUrl DomainServer::oauthRedirectURL() { return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort()); } @@ -983,30 +632,18 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) { return authorizationURL; } -int DomainServer::parseNodeData(QDataStream& packetStream, NodeType_t& nodeType, - HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, - const HifiSockAddr& senderSockAddr) { - packetStream >> nodeType; - packetStream >> publicSockAddr >> localSockAddr; +void DomainServer::handleConnectedNode(SharedNodePointer newNode) { - if (publicSockAddr.getAddress().isNull()) { - // this node wants to use us its STUN server - // so set the node public address to whatever we perceive the public address to be - - // if the sender is on our box then leave its public address to 0 so that - // other users attempt to reach it on the same address they have for the domain-server - if (senderSockAddr.getAddress().isLoopback()) { - publicSockAddr.setAddress(QHostAddress()); - } else { - publicSockAddr.setAddress(senderSockAddr.getAddress()); - } - } - - return packetStream.device()->pos(); + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + + // reply back to the user with a PacketType::DomainList + sendDomainListToNode(newNode, nodeData->getSendingSockAddr()); + + // send out this node to our other connected nodes + broadcastNewNode(newNode); } -void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr, - const NodeSet& nodeInterestSet) { +void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) { 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 @@ -1029,7 +666,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); // store the nodeInterestSet on this DomainServerNodeData, in case it has changed - nodeData->setNodeInterestSet(nodeInterestSet); + auto& nodeInterestSet = nodeData->getNodeInterestSet(); if (nodeInterestSet.size() > 0) { @@ -1169,11 +806,10 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer packe auto limitedNodeList = DependencyManager::get(); limitedNodeList->sendUnreliablePacket(*assignmentPacket, packet->getSenderSockAddr()); - // add the information for that deployed assignment to the hash of pending assigned nodes - PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(), - requestAssignment.getWalletUUID(), - requestAssignment.getNodeVersion()); - _pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData); + // give the information for that deployed assignment to the gatekeeper so it knows to that that node + // in when it comes back around + _gatekeeper.addPendingAssignedNode(uniqueAssignment.getUUID(), assignmentToDeploy->getUUID(), + requestAssignment.getWalletUUID(), requestAssignment.getNodeVersion()); } else { if (requestAssignment.getType() != Assignment::AgentType || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) { @@ -1253,30 +889,6 @@ void DomainServer::sendPendingTransactionsToServer() { } } -void DomainServer::publicKeyJSONCallback(QNetworkReply& requestReply) { - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - - if (jsonObject["status"].toString() == "success") { - // figure out which user this is for - - const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; - QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); - - if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { - QString username = usernameRegex.cap(1); - - qDebug() << "Storing a public key for user" << username; - - // pull the public key as a QByteArray from this response - const QString JSON_DATA_KEY = "data"; - const QString JSON_PUBLIC_KEY_KEY = "public_key"; - - _userPublicKeys[username] = - QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()); - } - } -} - void DomainServer::transactionJSONCallback(const QJsonObject& data) { // check if this was successful - if so we can remove it from our list of pending if (data.value("status").toString() == "success") { @@ -1373,91 +985,6 @@ void DomainServer::sendHeartbeatToIceServer() { DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); } -const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; - -void DomainServer::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { - - if (peer->getConnectionAttempts() > 0 && peer->getConnectionAttempts() % NUM_PEER_PINGS_BEFORE_DELETE == 0) { - // we've reached the maximum number of ping attempts - qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); - qDebug() << "Removing from list of connecting peers."; - - _icePeers.remove(peer->getUUID()); - } else { - auto limitedNodeList = DependencyManager::get(); - - // send the ping packet to the local and public sockets for this node - auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID()); - limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket()); - - auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID()); - limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket()); - - peer->incrementConnectionAttempts(); - } -} - -void DomainServer::handlePeerPingTimeout() { - NetworkPeer* senderPeer = qobject_cast(sender()); - - if (senderPeer) { - SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID()); - - if (sharedPeer && !sharedPeer->getActiveSocket()) { - pingPunchForConnectingPeer(sharedPeer); - } - } -} - -void DomainServer::processICEPeerInformationPacket(QSharedPointer packet) { - // loop through the packet and pull out network peers - // any peer we don't have we add to the hash, otherwise we update - QDataStream iceResponseStream(packet.data()); - - NetworkPeer* receivedPeer = new NetworkPeer; - iceResponseStream >> *receivedPeer; - - if (!_icePeers.contains(receivedPeer->getUUID())) { - qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer; - SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer); - _icePeers[receivedPeer->getUUID()] = newPeer; - - // make sure we know when we should ping this peer - connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainServer::handlePeerPingTimeout); - - // immediately ping the new peer, and start a timer to continue pinging it until we connect to it - newPeer->startPingTimer(); - - qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" - << newPeer->getUUID(); - - pingPunchForConnectingPeer(newPeer); - } else { - delete receivedPeer; - } -} - -void DomainServer::processICEPingPacket(QSharedPointer packet) { - auto limitedNodeList = DependencyManager::get(); - auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*packet, limitedNodeList->getSessionUUID()); - - limitedNodeList->sendPacket(std::move(pingReplyPacket), packet->getSenderSockAddr()); -} - -void DomainServer::processICEPingReplyPacket(QSharedPointer packet) { - QDataStream packetStream(packet.data()); - - QUuid nodeUUID; - packetStream >> nodeUUID; - - SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID); - - if (sendingPeer) { - // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list - sendingPeer->activateMatchingOrNewSymmetricSocket(packet->getSenderSockAddr()); - } -} - void DomainServer::processNodeJSONStatsPacket(QSharedPointer packet, SharedNodePointer sendingNode) { auto nodeData = dynamic_cast(sendingNode->getLinkedData()); if (nodeData) { @@ -1569,9 +1096,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url SharedAssignmentPointer matchingAssignment = _allAssignments.value(matchingUUID); if (!matchingAssignment) { // check if we have a pending assignment that matches this temp UUID, and it is a scripted assignment - PendingAssignedNodeData* pendingData = _pendingAssignedNodes.value(matchingUUID); - if (pendingData) { - matchingAssignment = _allAssignments.value(pendingData->getAssignmentUUID()); + QUuid assignmentUUID = _gatekeeper.assignmentUUIDForPendingAssignment(matchingUUID); + if (!assignmentUUID.isNull()) { + matchingAssignment = _allAssignments.value(assignmentUUID); if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) { // we have a matching assignment and it is for the right type, have the HTTP manager handle it @@ -1579,7 +1106,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUrl scriptURL = url; scriptURL.setPath(URI_ASSIGNMENT + "/scripts/" - + uuidStringWithoutCurlyBraces(pendingData->getAssignmentUUID())); + + uuidStringWithoutCurlyBraces(assignmentUUID)); // have the HTTPManager serve the appropriate script file return _httpManager.handleHTTPRequest(connection, scriptURL, true); @@ -2113,7 +1640,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) { void DomainServer::nodeKilled(SharedNodePointer node) { // if this peer connected via ICE then remove them from our ICE peers hash - _icePeers.remove(node->getUUID()); + _gatekeeper.removeICEPeer(node->getUUID()); DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); @@ -2141,7 +1668,7 @@ void DomainServer::nodeKilled(SharedNodePointer node) { } } -SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& assignmentUUID, NodeType_t nodeType) { +SharedAssignmentPointer DomainServer::dequeueMatchingAssignment(const QUuid& assignmentUUID, NodeType_t nodeType) { QQueue::iterator i = _unfulfilledAssignments.begin(); while (i != _unfulfilledAssignments.end()) { @@ -2192,20 +1719,6 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig return SharedAssignmentPointer(); } -void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) { - QQueue::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin(); - while (potentialMatchingAssignment != _unfulfilledAssignments.end()) { - if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) { - _unfulfilledAssignments.erase(potentialMatchingAssignment); - - // we matched and removed an assignment, bail out - break; - } else { - ++potentialMatchingAssignment; - } - } -} - void DomainServer::addStaticAssignmentsToQueue() { // if the domain-server has just restarted, diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 7495e080de..a7e1a37c10 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -25,6 +25,7 @@ #include #include +#include "DomainGatekeeper.h" #include "DomainServerSettingsManager.h" #include "DomainServerWebSessionData.h" #include "WalletTransaction.h" @@ -50,19 +51,14 @@ public slots: /// Called by NodeList to inform us a node has been killed void nodeKilled(SharedNodePointer node); - void publicKeyJSONCallback(QNetworkReply& requestReply); void transactionJSONCallback(const QJsonObject& data); void restart(); void processRequestAssignmentPacket(QSharedPointer packet); - void processConnectRequestPacket(QSharedPointer packet); void processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void processNodeJSONStatsPacket(QSharedPointer packet, SharedNodePointer sendingNode); void processPathQueryPacket(QSharedPointer packet); - void processICEPingPacket(QSharedPointer packet); - void processICEPingReplyPacket(QSharedPointer packet); - void processICEPeerInformationPacket(QSharedPointer packet); private slots: void aboutToQuit(); @@ -74,7 +70,9 @@ private slots: void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } void sendHeartbeatToIceServer(); - void handlePeerPingTimeout(); + + void handleConnectedNode(SharedNodePointer newNode); + private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); @@ -87,20 +85,9 @@ private: void setupAutomaticNetworking(); void sendHeartbeatToDataServer(const QString& networkAddress); - void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); - unsigned int countConnectedUsers(); - bool verifyUserSignature (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn); - bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, - const HifiSockAddr& senderSockAddr, QString& reasonReturn); - void preloadAllowedUserPublicKeys(); - void requestUserPublicKey(const QString& username); - - int parseNodeData(QDataStream& packetStream, NodeType_t& nodeType, - HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const HifiSockAddr& senderSockAddr); - void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, - const NodeSet& nodeInterestSet); + void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr); QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); void broadcastNewNode(const SharedNodePointer& node); @@ -111,12 +98,11 @@ private: void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes); void populateStaticScriptedAssignmentsFromSettings(); - SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType); + SharedAssignmentPointer dequeueMatchingAssignment(const QUuid& checkInUUID, NodeType_t nodeType); SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment); - void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment); void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); void addStaticAssignmentsToQueue(); - + QUrl oauthRedirectURL(); QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid()); @@ -131,13 +117,14 @@ private: QJsonObject jsonForSocket(const HifiSockAddr& socket); QJsonObject jsonObjectForNode(const SharedNodePointer& node); + + DomainGatekeeper _gatekeeper; HTTPManager _httpManager; HTTPSManager* _httpsManager; QHash _allAssignments; QQueue _unfulfilledAssignments; - QHash _pendingAssignedNodes; TransactionHash _pendingAssignmentCredits; bool _isUsingDTLS; @@ -149,18 +136,14 @@ private: QSet _webAuthenticationStateSet; QHash _cookieSessionHash; - - QHash _connectionTokenHash; - - QHash _userPublicKeys; - - QHash _icePeers; QString _automaticNetworkingSetting; DomainServerSettingsManager _settingsManager; HifiSockAddr _iceServerSocket; + + friend class DomainGatekeeper; }; diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp new file mode 100644 index 0000000000..6e6e35f205 --- /dev/null +++ b/domain-server/src/NodeConnectionData.cpp @@ -0,0 +1,37 @@ +// +// NodeConnectionData.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2015-08-24. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "NodeConnectionData.h" + +NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr) { + NodeConnectionData newHeader; + + dataStream >> newHeader.connectUUID >> newHeader.nodeType + >> newHeader.publicSockAddr >> newHeader.localSockAddr + >> newHeader.interestList; + + newHeader.senderSockAddr = senderSockAddr; + + if (newHeader.publicSockAddr.getAddress().isNull()) { + // this node wants to use us its STUN server + // so set the node public address to whatever we perceive the public address to be + + // if the sender is on our box then leave its public address to 0 so that + // other users attempt to reach it on the same address they have for the domain-server + if (senderSockAddr.getAddress().isLoopback()) { + newHeader.publicSockAddr.setAddress(QHostAddress()); + } else { + newHeader.publicSockAddr.setAddress(senderSockAddr.getAddress()); + } + } + + return newHeader; +} diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h new file mode 100644 index 0000000000..60b0e9edfa --- /dev/null +++ b/domain-server/src/NodeConnectionData.h @@ -0,0 +1,32 @@ +// +// NodeConnectionData.h +// domain-server/src +// +// Created by Stephen Birarda on 2015-08-24. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_NodeConnectionData_h +#define hifi_NodeConnectionData_h + +#include + +class NodeConnectionData { +public: + static NodeConnectionData fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr); + + QUuid connectUUID; + NodeType_t nodeType; + HifiSockAddr publicSockAddr; + HifiSockAddr localSockAddr; + HifiSockAddr senderSockAddr; + QList interestList; +}; + + +#endif // hifi_NodeConnectionData_h diff --git a/domain-server/src/PendingAssignedNodeData.cpp b/domain-server/src/PendingAssignedNodeData.cpp index 30310ac01f..e0b6b68eb5 100644 --- a/domain-server/src/PendingAssignedNodeData.cpp +++ b/domain-server/src/PendingAssignedNodeData.cpp @@ -11,10 +11,12 @@ #include "PendingAssignedNodeData.h" +#include + PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID, const QString& nodeVersion) : _assignmentUUID(assignmentUUID), _walletUUID(walletUUID), _nodeVersion(nodeVersion) { - -} \ No newline at end of file + +} diff --git a/domain-server/src/PendingAssignedNodeData.h b/domain-server/src/PendingAssignedNodeData.h index 2d546f573f..eb384a7a93 100644 --- a/domain-server/src/PendingAssignedNodeData.h +++ b/domain-server/src/PendingAssignedNodeData.h @@ -34,4 +34,4 @@ private: QString _nodeVersion; }; -#endif // hifi_PendingAssignedNodeData_h \ No newline at end of file +#endif // hifi_PendingAssignedNodeData_h diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 33d490c960..88a3ccf8ff 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -138,7 +138,7 @@ public: SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool canAdjustLocks, bool canRez, + bool canAdjustLocks = false, bool canRez = false, const QUuid& connectionSecret = QUuid()); bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } diff --git a/libraries/networking/src/UUIDHasher.h b/libraries/networking/src/UUIDHasher.h index 5429e151c7..b05e517841 100644 --- a/libraries/networking/src/UUIDHasher.h +++ b/libraries/networking/src/UUIDHasher.h @@ -20,10 +20,14 @@ class UUIDHasher { public: size_t operator()(const QUuid& uuid) const { - return uuid.data1 ^ uuid.data2 ^ (uuid.data3 << 16) - ^ ((uuid.data4[0] << 24) | (uuid.data4[1] << 16) | (uuid.data4[2] << 8) | uuid.data4[3]) - ^ ((uuid.data4[4] << 24) | (uuid.data4[5] << 16) | (uuid.data4[6] << 8) | uuid.data4[7]); + return qHash(uuid); } }; -#endif // hifi_UUIDHasher_h \ No newline at end of file +template <> struct std::hash { + size_t operator()(const QUuid& uuid) const { + return qHash(uuid); + } +}; + +#endif // hifi_UUIDHasher_h diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index 8c77a1e388..65d5315206 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -18,14 +18,6 @@ #include "JurisdictionMap.h" #include "SentPacketHistory.h" -namespace std { - template <> struct hash { - size_t operator()(const QUuid& uuid) const { - return qHash(uuid); - } - }; -} - /// Utility for processing, packing, queueing and sending of outbound edit messages. class OctreeEditPacketSender : public PacketSender { Q_OBJECT From d2acf327dd585092895a8ad26c6cda052c2852bc Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 14:43:49 -0700 Subject: [PATCH 02/11] fix NodeConnectionData discrepancy on request --- domain-server/src/DomainGatekeeper.cpp | 12 ++++++++---- domain-server/src/DomainServer.cpp | 3 ++- domain-server/src/NodeConnectionData.cpp | 9 +++++++-- domain-server/src/NodeConnectionData.h | 3 ++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f712229e2d..9578e0565f 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -168,6 +168,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (usernameSignature.isEmpty()) { // if user didn't include usernameSignature in connect request, send a connectionToken packet sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); + + // ask for their public key right now to make sure we have it + requestUserPublicKey(username); + return SharedNodePointer(); } } @@ -205,11 +209,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - bool isAllowedEditor = allowedEditors.isEmpty() || allowedEditors.contains(username); + bool canAdjustLocks = allowedEditors.empty(); - bool canAdjustLocks = false; - - if (isAllowedEditor) { + if (allowedEditors.contains(username)) { if (!verifiedUsername) { if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) { qDebug() << "Could not verify user" << username << "as allowed editor. User will still be allowed to connect" @@ -394,6 +396,8 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA if (allowedEditors.contains(username)) { if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) { verifiedUsername = true; + qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity << + "but user" << username << "is in allowed editors list so will be allowed to connect."; return true; } } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 12a9e094aa..392c09bd8a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -574,7 +574,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet packet, SharedNodePointer sendingNode) { QDataStream packetStream(packet.data()); - NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr()); + NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr(), false); // update this node's sockets in case they have changed sendingNode->setPublicSocket(nodeRequestData.publicSockAddr); @@ -675,6 +675,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif // if this authenticated node has any interest types, send back those nodes as well limitedNodeList->eachNode([&](const SharedNodePointer& otherNode){ if (otherNode->getUUID() != node->getUUID() && nodeInterestSet.contains(otherNode->getType())) { + // since we're about to add a node to the packet we start a segment domainListPackets.startSegment(); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 6e6e35f205..3c75e8faad 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -11,10 +11,15 @@ #include "NodeConnectionData.h" -NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr) { +NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr, + bool isConnectRequest) { NodeConnectionData newHeader; - dataStream >> newHeader.connectUUID >> newHeader.nodeType + if (isConnectRequest) { + dataStream >> newHeader.connectUUID; + } + + dataStream >> newHeader.nodeType >> newHeader.publicSockAddr >> newHeader.localSockAddr >> newHeader.interestList; diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 60b0e9edfa..6b3b8eb7c1 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -18,7 +18,8 @@ class NodeConnectionData { public: - static NodeConnectionData fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr); + static NodeConnectionData fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr, + bool isConnectRequest = true); QUuid connectUUID; NodeType_t nodeType; From 7591a29c48fff7c71cef166dd9ab72dc8f26d2ec Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 14:44:24 -0700 Subject: [PATCH 03/11] add back indentation to STATICALLY_ASSIGNED_NODES --- domain-server/src/DomainGatekeeper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9578e0565f..08f60e6d37 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -48,7 +48,7 @@ QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID } const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer -<< NodeType::AvatarMixer << NodeType::EntityServer; + << NodeType::AvatarMixer << NodeType::EntityServer; void DomainGatekeeper::processConnectRequestPacket(QSharedPointer packet) { if (packet->getPayloadSize() == 0) { From d57c1fc2d52740d768f7286edb9711dc0e27e590 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 14:50:18 -0700 Subject: [PATCH 04/11] fix a typo in DomainGatekeeper comment --- domain-server/src/DomainGatekeeper.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 08f60e6d37..7746f658cf 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -103,10 +103,14 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, const PendingAssignedNodeData& pendingAssignment) { - SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); + + // make sure this matches an assignment the DS told us we sent out auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID); + SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); + if (it != _pendingAssignedNodes.end()) { + // find the matching queued static assignment in DS queue matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType); if (matchingQueuedAssignment) { @@ -124,12 +128,12 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo return SharedNodePointer(); } + // add the new node SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); - // when the newNode is created the linked data is also created - // if this was a static assignment set the UUID, set the sendingSockAddr DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + // set assignment related data on the linked data for this node nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setWalletUUID(it->second.getWalletUUID()); nodeData->setNodeVersion(it->second.getNodeVersion()); @@ -157,7 +161,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect bool isRestrictingAccess = _server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); - // check if this user is on our local machine - is this is true they are always allowed to connect + // check if this user is on our local machine - if this is true they are always allowed to connect QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); @@ -202,6 +206,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect } } + // add the new node SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); // if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true @@ -209,23 +214,30 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); + // if the allowed editors list is empty then everyone can adjust locks bool canAdjustLocks = allowedEditors.empty(); if (allowedEditors.contains(username)) { + // we have a non-empty allowed editors list - check if this user is verified to be in it if (!verifiedUsername) { if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) { + // failed to verify a user that is in the allowed editors list + qDebug() << "Could not verify user" << username << "as allowed editor. User will still be allowed to connect" << "but will not have edit privileges."; - canAdjustLocks = true; + canAdjustLocks = false; } else { + // just verified this user and they are in the allowed editors list canAdjustLocks = true; } } else { + // already verified this user and they are in the allowed editors list canAdjustLocks = true; } } + // check if only editors should be able to rez entities const QVariant* editorsAreRezzersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); @@ -239,6 +251,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect canRez = canAdjustLocks; } + // grab the linked data for our new node so we can set the username DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); // if we have a username from the connect request, set it on the DomainServerNodeData From fe591f61ccd52c3818f6b8cad809a62020af34d2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 14:54:48 -0700 Subject: [PATCH 05/11] fix class rename in processConnectRequestPacket --- domain-server/src/DomainGatekeeper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 7746f658cf..d272ab3a84 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -57,7 +57,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack QDataStream packetStream(packet.data()); - // read a NodePacketHeader from the packet so we can pass around this data while we're inspecting it + // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr()); if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { From c6f1567361dadd561a7cad4ab84f6a12940a1f25 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 14:58:25 -0700 Subject: [PATCH 06/11] add a debug for refused connection in DomainGatekeeper --- domain-server/src/DomainGatekeeper.cpp | 2 ++ domain-server/src/PendingAssignedNodeData.cpp | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index d272ab3a84..1e040f62ed 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -98,6 +98,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away emit connectedNode(node); + } else { + qDebug() << "Refusing connection from node at" << packet->getSenderSockAddr(); } } diff --git a/domain-server/src/PendingAssignedNodeData.cpp b/domain-server/src/PendingAssignedNodeData.cpp index e0b6b68eb5..1376e6aa0e 100644 --- a/domain-server/src/PendingAssignedNodeData.cpp +++ b/domain-server/src/PendingAssignedNodeData.cpp @@ -11,8 +11,6 @@ #include "PendingAssignedNodeData.h" -#include - PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID, const QString& nodeVersion) : _assignmentUUID(assignmentUUID), _walletUUID(walletUUID), From a9c04e5116a7f8b58ceea2789b6041db314c7128 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 15:01:39 -0700 Subject: [PATCH 07/11] don't allow verified connection without username --- domain-server/src/DomainGatekeeper.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 1e040f62ed..a86ce068c3 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -367,17 +367,18 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr) { + if (username.isEmpty()) { + qDebug() << "Connect request denied - no username provided."; + + sendConnectionDeniedPacket("No username provided", senderSockAddr); + + return false; + } + QStringList allowedUsers = _server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); if (allowedUsers.contains(username, Qt::CaseInsensitive)) { - if (username.isEmpty()) { - qDebug() << "Connect request denied - no username provided."; - - sendConnectionDeniedPacket("No username provided", senderSockAddr); - - return false; - } if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) { return false; } From d02e5b2450c6ea7d880dccdd4b3195569f2f6cf1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 15:10:27 -0700 Subject: [PATCH 08/11] fix a typo in comment in DomainGatekeeper --- domain-server/src/DomainGatekeeper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index a86ce068c3..01e98868ec 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -492,7 +492,7 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H // setup the DomainConnectionDenied packet auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); - // packet in the reason the connection was denied (the client displays this) + // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { connectionDeniedPacket->writePrimitive(payloadSize); connectionDeniedPacket->write(utfString); From c8ba24817f37459f1c7c7934184d5d95db11daa2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Aug 2015 17:09:52 -0700 Subject: [PATCH 09/11] fix for hash specialization for QUuid --- libraries/networking/src/UUIDHasher.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/UUIDHasher.h b/libraries/networking/src/UUIDHasher.h index b05e517841..0bdfe9792a 100644 --- a/libraries/networking/src/UUIDHasher.h +++ b/libraries/networking/src/UUIDHasher.h @@ -24,10 +24,13 @@ public: } }; -template <> struct std::hash { - size_t operator()(const QUuid& uuid) const { - return qHash(uuid); - } -}; +namespace std { + template <> struct hash { + size_t operator()(const QUuid& uuid) const { + return qHash(uuid); + } + }; +} + #endif // hifi_UUIDHasher_h From 9940f375ea3e368dae626d2d5dad3b1b002113b0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Aug 2015 10:02:43 -0700 Subject: [PATCH 10/11] repairs for CR comments --- domain-server/src/DomainGatekeeper.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 01e98868ec..efd156e1a9 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -253,6 +253,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect canRez = canAdjustLocks; } + // set the edit rights for this user + newNode->setCanAdjustLocks(canAdjustLocks); + newNode->setCanRez(canRez); + // grab the linked data for our new node so we can set the username DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); @@ -527,7 +531,7 @@ const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { - if (peer->getConnectionAttempts() > 0 && peer->getConnectionAttempts() % NUM_PEER_PINGS_BEFORE_DELETE == 0) { + if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) { // we've reached the maximum number of ping attempts qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); qDebug() << "Removing from list of connecting peers."; From c776f04ab2ce4eabcf74d459a8270cd4985bd398 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Aug 2015 14:36:22 -0700 Subject: [PATCH 11/11] don't allow in a user who should have edit but not verified --- domain-server/src/DomainGatekeeper.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index efd156e1a9..72b353f8a0 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -208,9 +208,6 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect } } - // add the new node - SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); - // if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true const QVariant* allowedEditorsVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); @@ -222,13 +219,13 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (allowedEditors.contains(username)) { // we have a non-empty allowed editors list - check if this user is verified to be in it if (!verifiedUsername) { - if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) { + if (!verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // failed to verify a user that is in the allowed editors list + sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); - qDebug() << "Could not verify user" << username << "as allowed editor. User will still be allowed to connect" - << "but will not have edit privileges."; + qDebug() << "Could not verify user" << username << "as allowed editor. Forcing user to attempt reconnect."; - canAdjustLocks = false; + return SharedNodePointer(); } else { // just verified this user and they are in the allowed editors list canAdjustLocks = true; @@ -253,6 +250,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect canRez = canAdjustLocks; } + // add the new node + SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); + // set the edit rights for this user newNode->setCanAdjustLocks(canAdjustLocks); newNode->setCanRez(canRez);