void NetworkInterfaceASIO::_asyncRunCommand(AsyncOp* op, NetworkOpHandler handler) { LOG(2) << "Starting asynchronous command " << op->request().id << " on host " << op->request().target.toString(); if (MONGO_FAIL_POINT(NetworkInterfaceASIOasyncRunCommandFail)) { _validateAndRun(op, asio::error::basic_errors::network_unreachable, [] {}); return; } // We invert the following steps below to run a command: // 1 - send the given command // 2 - receive a header for the response // 3 - validate and receive response body // 4 - advance the state machine by calling handler() auto cmd = op->command(); // Step 4 auto recvMessageCallback = [this, cmd, handler, op](std::error_code ec, size_t bytes) { // We don't call _validateAndRun here as we assume the caller will. handler(ec, bytes); }; // Step 3 auto recvHeaderCallback = [this, cmd, handler, recvMessageCallback, op](std::error_code ec, size_t bytes) { // The operation could have been canceled after starting the command, but before // receiving the header _validateAndRun(op, ec, [this, op, recvMessageCallback, ec, bytes, cmd, handler] { // validate response id uint32_t expectedId = cmd->toSend().header().getId(); uint32_t actualId = cmd->header().constView().getResponseToMsgId(); if (actualId != expectedId) { LOG(3) << "got wrong response:" << " expected response id: " << expectedId << ", got response id: " << actualId; return handler(make_error_code(ErrorCodes::ProtocolError), bytes); } asyncRecvMessageBody(cmd->conn().stream(), &cmd->header(), &cmd->toRecv(), std::move(recvMessageCallback)); }); }; // Step 2 auto sendMessageCallback = [this, cmd, handler, recvHeaderCallback, op](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, cmd, op, recvHeaderCallback] { asyncRecvMessageHeader( cmd->conn().stream(), &cmd->header(), std::move(recvHeaderCallback)); }); }; // Step 1 asyncSendMessage(cmd->conn().stream(), &cmd->toSend(), std::move(sendMessageCallback)); }
void NetworkInterfaceASIO::_asyncSendSimpleMessage(AsyncOp* op, const asio::const_buffer& buf) { asio::async_write(op->connection()->sock(), asio::buffer(buf), [this, op](std::error_code ec, std::size_t bytes) { _validateAndRun(op, ec, [this, op]() { _receiveResponse(op); }); }); }
void NetworkInterfaceASIO::_beginCommunication(AsyncOp* op) { // The way that we connect connections for the connection pool is by // starting the callback chain with connect(), but getting off at the first // _beginCommunication. I.e. all AsyncOp's start off with _inSetup == true // and arrive here as they're connected and authed. Once they hit here, we // return to the connection pool's get() callback with _inSetup == false, // so we can proceed with user operations after they return to this // codepath. if (op->_inSetup) { log() << "Successfully connected to " << op->request().target.toString(); op->_inSetup = false; op->finish(RemoteCommandResponse()); return; } LOG(3) << "Initiating asynchronous command: " << redact(op->request().toString()); auto beginStatus = op->beginCommand(op->request()); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } _asyncRunCommand(op, [this, op](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, op]() { _completedOpCallback(op); }); }); }
void NetworkInterfaceASIO::_recvMessageHeader(AsyncOp* op) { asio::async_read(op->connection()->sock(), asio::buffer(reinterpret_cast<char*>(op->header()), sizeof(MSGHEADER::Value)), [this, op](asio::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, op]() { _recvMessageBody(op); }); }); }
void NetworkInterfaceASIO::_runIsMaster(AsyncOp* op) { // We use a legacy builder to create our ismaster request because we may // have to communicate with servers that do not support OP_COMMAND rpc::LegacyRequestBuilder requestBuilder{}; requestBuilder.setDatabase("admin"); requestBuilder.setCommandName("isMaster"); requestBuilder.setMetadata(rpc::makeEmptyMetadata()); requestBuilder.setCommandArgs(BSON("isMaster" << 1)); // Set current command to ismaster request and run auto beginStatus = op->beginCommand(std::move(*(requestBuilder.done()))); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } // Callback to parse protocol information out of received ismaster response auto parseIsMaster = [this, op]() { auto swCommandReply = op->command()->response(rpc::Protocol::kOpQuery, now()); if (!swCommandReply.isOK()) { return _completeOperation(op, swCommandReply.getStatus()); } auto commandReply = std::move(swCommandReply.getValue()); if (_hook) { // Run the validation hook. auto validHost = callNoexcept( *_hook, &NetworkConnectionHook::validateHost, op->request().target, commandReply); if (!validHost.isOK()) { return _completeOperation(op, validHost); } } auto protocolSet = rpc::parseProtocolSetFromIsMasterReply(commandReply.data); if (!protocolSet.isOK()) return _completeOperation(op, protocolSet.getStatus()); op->connection().setServerProtocols(protocolSet.getValue()); // Set the operation protocol auto negotiatedProtocol = rpc::negotiate(op->connection().serverProtocols(), op->connection().clientProtocols()); if (!negotiatedProtocol.isOK()) { return _completeOperation(op, negotiatedProtocol.getStatus()); } op->setOperationProtocol(negotiatedProtocol.getValue()); return _authenticate(op); }; _asyncRunCommand(op->command(), [this, op, parseIsMaster](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, std::move(parseIsMaster)); }); }
void NetworkInterfaceASIO::_beginCommunication(AsyncOp* op) { auto& cmd = op->beginCommand(op->request(), op->operationProtocol()); _asyncRunCommand(&cmd, [this, op](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, op]() { _completedOpCallback(op); }); }); }
void NetworkInterfaceASIO::_authenticate(AsyncOp* op) { // There is currently no way for NetworkInterfaceASIO's users to run a command // without going through _authenticate(). Callers may want to run certain commands, // such as ismasters, pre-auth. We may want to offer this choice in the future. // This check is sufficient to see if auth is enabled on the system, // and avoids creating dependencies on deeper, less accessible auth code. if (!isInternalAuthSet()) { return _runConnectionHook(op); } // We will only have a valid clientName if SSL is enabled. std::string clientName; #ifdef MONGO_CONFIG_SSL if (getSSLManager()) { clientName = getSSLManager()->getSSLConfiguration().clientSubjectName; } #endif // authenticateClient will use this to run auth-related commands over our connection. auto runCommandHook = [this, op](executor::RemoteCommandRequest request, auth::AuthCompletionHandler handler) { // SERVER-14170: Set the metadataHook to nullptr explicitly as we cannot write metadata // here. auto beginStatus = op->beginCommand(request); if (!beginStatus.isOK()) { return handler(beginStatus); } auto callAuthCompletionHandler = [this, op, handler]() { auto authResponse = op->command()->response(op, op->operationProtocol(), now(), nullptr); handler(authResponse); }; _asyncRunCommand(op, [this, op, callAuthCompletionHandler](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, callAuthCompletionHandler); }); }; // This will be called when authentication has completed. auto authHook = [this, op](auth::AuthResponse response) { if (!response.isOK()) return _completeOperation(op, response); return _runConnectionHook(op); }; auto params = getInternalUserAuthParams(); auth::authenticateClient( params, op->request().target.host(), clientName, runCommandHook, authHook); }
void NetworkInterfaceASIO::_runIsMaster(AsyncOp* op) { // We use a legacy builder to create our ismaster request because we may // have to communicate with servers that do not support OP_COMMAND rpc::LegacyRequestBuilder requestBuilder{}; requestBuilder.setDatabase("admin"); requestBuilder.setCommandName("isMaster"); requestBuilder.setMetadata(rpc::makeEmptyMetadata()); requestBuilder.setCommandArgs(BSON("isMaster" << 1)); // Set current command to ismaster request and run auto& cmd = op->beginCommand(std::move(*(requestBuilder.done()))); // Callback to parse protocol information out of received ismaster response auto parseIsMaster = [this, op]() { try { auto commandReply = rpc::makeReply(&(op->command().toRecv())); BSONObj isMasterReply = commandReply->getCommandReply(); auto protocolSet = rpc::parseProtocolSetFromIsMasterReply(isMasterReply); if (!protocolSet.isOK()) return _completeOperation(op, protocolSet.getStatus()); op->connection().setServerProtocols(protocolSet.getValue()); // Set the operation protocol auto negotiatedProtocol = rpc::negotiate(op->connection().serverProtocols(), op->connection().clientProtocols()); if (!negotiatedProtocol.isOK()) { return _completeOperation(op, negotiatedProtocol.getStatus()); } op->setOperationProtocol(negotiatedProtocol.getValue()); // Advance the state machine return _authenticate(op); } catch (...) { // makeReply will throw if the reply was invalid. return _completeOperation(op, exceptionToStatus()); } }; _asyncRunCommand(&cmd, [this, op, parseIsMaster](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, std::move(parseIsMaster)); }); }
void NetworkInterfaceASIO::_recvMessageBody(AsyncOp* op) { // TODO: This error code should be more meaningful. std::error_code ec; // validate message length int len = op->header()->constView().getMessageLength(); if (len == 542393671) { LOG(3) << "attempt to access MongoDB over HTTP on the native driver port."; return _networkErrorCallback(op, ec); } else if (len == -1) { // TODO: An endian check is run after the client connects, we should // set that we've received the client's handshake LOG(3) << "Endian check received from client"; return _networkErrorCallback(op, ec); } else if (static_cast<size_t>(len) < sizeof(MSGHEADER::Value) || static_cast<size_t>(len) > MaxMessageSizeBytes) { warning() << "recv(): message len " << len << " is invalid. " << "Min " << sizeof(MSGHEADER::Value) << " Max: " << MaxMessageSizeBytes; return _networkErrorCallback(op, ec); } // validate response id uint32_t expectedId = op->toSend()->header().getId(); uint32_t actualId = op->header()->constView().getResponseTo(); if (actualId != expectedId) { LOG(3) << "got wrong response:" << " expected response id: " << expectedId << ", got response id: " << actualId; return _networkErrorCallback(op, ec); } int z = (len + 1023) & 0xfffffc00; invariant(z >= len); op->toRecv()->setData(reinterpret_cast<char*>(mongoMalloc(z)), true); MsgData::View mdView = op->toRecv()->buf(); // copy header data into master buffer int headerLen = sizeof(MSGHEADER::Value); memcpy(mdView.view2ptr(), op->header(), headerLen); int bodyLength = len - headerLen; invariant(bodyLength >= 0); // receive remaining data into md->data asio::async_read(op->connection()->sock(), asio::buffer(mdView.data(), bodyLength), [this, op, mdView](asio::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, op]() { _completedWriteCallback(op); }); }); }
void NetworkInterfaceASIO::_runConnectionHook(AsyncOp* op) { if (!_hook) { return _beginCommunication(op); } auto swOptionalRequest = callNoexcept(*_hook, &NetworkConnectionHook::makeRequest, op->request().target); if (!swOptionalRequest.isOK()) { return _completeOperation(op, swOptionalRequest.getStatus()); } auto optionalRequest = std::move(swOptionalRequest.getValue()); if (optionalRequest == boost::none) { return _beginCommunication(op); } auto beginStatus = op->beginCommand(*optionalRequest, _metadataHook.get()); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } auto finishHook = [this, op]() { auto response = op->command()->response(op->operationProtocol(), now(), _metadataHook.get()); if (!response.isOK()) { return _completeOperation(op, response.getStatus()); } auto handleStatus = callNoexcept(*_hook, &NetworkConnectionHook::handleReply, op->request().target, std::move(response.getValue())); if (!handleStatus.isOK()) { return _completeOperation(op, handleStatus); } return _beginCommunication(op); }; return _asyncRunCommand(op, [this, op, finishHook](std::error_code ec, std::size_t bytes) { _validateAndRun(op, ec, finishHook); }); }
void NetworkInterfaceASIO::_beginCommunication(AsyncOp* op) { auto negotiatedProtocol = rpc::negotiate(op->connection().serverProtocols(), op->connection().clientProtocols()); if (!negotiatedProtocol.isOK()) { return _completeOperation(op, negotiatedProtocol.getStatus()); } op->setOperationProtocol(negotiatedProtocol.getValue()); auto& cmd = op->beginCommand( std::move(*_messageFromRequest(op->request(), negotiatedProtocol.getValue()))); _asyncRunCommand(&cmd, [this, op](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, op]() { _completedOpCallback(op); }); }); }
void NetworkInterfaceASIO::_runIsMaster(AsyncOp* op) { // We use a legacy builder to create our ismaster request because we may // have to communicate with servers that do not support OP_COMMAND rpc::LegacyRequestBuilder requestBuilder{}; requestBuilder.setDatabase("admin"); requestBuilder.setCommandName("isMaster"); BSONObjBuilder bob; bob.append("isMaster", 1); bob.append("hangUpOnStepDown", false); const auto versionString = VersionInfoInterface::instance().version(); ClientMetadata::serialize(_options.instanceName, versionString, &bob); if (Command::testCommandsEnabled) { // Only include the host:port of this process in the isMaster command request if test // commands are enabled. mongobridge uses this field to identify the process opening a // connection to it. StringBuilder sb; sb << getHostName() << ':' << serverGlobalParams.port; bob.append("hostInfo", sb.str()); } op->connection().getCompressorManager().clientBegin(&bob); if (WireSpec::instance().isInternalClient) { WireSpec::appendInternalClientWireVersion(WireSpec::instance().outgoing, &bob); } requestBuilder.setCommandArgs(bob.done()); requestBuilder.setMetadata(rpc::makeEmptyMetadata()); // Set current command to ismaster request and run auto beginStatus = op->beginCommand(requestBuilder.done(), op->request().target); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } // Callback to parse protocol information out of received ismaster response auto parseIsMaster = [this, op]() { auto swCommandReply = op->command()->response(op, rpc::Protocol::kOpQuery, now()); if (!swCommandReply.isOK()) { return _completeOperation(op, swCommandReply); } auto commandReply = std::move(swCommandReply); // Ensure that the isMaster response is "ok:1". auto commandStatus = getStatusFromCommandResult(commandReply.data); if (!commandStatus.isOK()) { return _completeOperation(op, commandStatus); } auto protocolSet = rpc::parseProtocolSetFromIsMasterReply(commandReply.data); if (!protocolSet.isOK()) return _completeOperation(op, protocolSet.getStatus()); auto validateStatus = rpc::validateWireVersion(WireSpec::instance().outgoing, protocolSet.getValue().version); if (!validateStatus.isOK()) { warning() << "remote host has incompatible wire version: " << validateStatus; return _completeOperation(op, validateStatus); } op->connection().setServerProtocols(protocolSet.getValue().protocolSet); invariant(op->connection().clientProtocols() != rpc::supports::kNone); // Set the operation protocol auto negotiatedProtocol = rpc::negotiate(op->connection().serverProtocols(), op->connection().clientProtocols()); if (!negotiatedProtocol.isOK()) { // Add relatively verbose logging here, since this should not happen unless we are // mongos and we try to connect to a node that doesn't support OP_COMMAND. warning() << "failed to negotiate protocol with remote host: " << op->request().target; warning() << "request was: " << redact(op->request().cmdObj); warning() << "response was: " << redact(commandReply.data); auto clientProtos = rpc::toString(op->connection().clientProtocols()); if (clientProtos.isOK()) { warning() << "our (client) supported protocols: " << clientProtos.getValue(); } auto serverProtos = rpc::toString(op->connection().serverProtocols()); if (serverProtos.isOK()) { warning() << "remote server's supported protocols:" << serverProtos.getValue(); } return _completeOperation(op, negotiatedProtocol.getStatus()); } op->setOperationProtocol(negotiatedProtocol.getValue()); op->connection().getCompressorManager().clientFinish(commandReply.data); if (_hook) { // Run the validation hook. auto validHost = callNoexcept( *_hook, &NetworkConnectionHook::validateHost, op->request().target, commandReply); if (!validHost.isOK()) { return _completeOperation(op, validHost); } } return _authenticate(op); }; _asyncRunCommand(op, [this, op, parseIsMaster](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, std::move(parseIsMaster)); }); }