diff options
-rw-r--r-- | http/http_connection.h | 4 | ||||
-rw-r--r-- | http/http_request.h | 14 | ||||
-rw-r--r-- | include/dump_offload.hpp | 305 |
3 files changed, 322 insertions, 1 deletions
diff --git a/http/http_connection.h b/http/http_connection.h index b7861751bb..6d28405df6 100644 --- a/http/http_connection.h +++ b/http/http_connection.h @@ -517,6 +517,10 @@ class Connection : public std::enable_shared_from_this< if (!isInvalidRequest) { + req->socket = [this, self = shared_from_this()]() -> Adaptor& { + return self->socket(); + }; + res.completeRequestHandler = [] {}; res.isAliveHelper = [this]() -> bool { return isAlive(); }; diff --git a/http/http_request.h b/http/http_request.h index b440f4447a..0dd7e35f95 100644 --- a/http/http_request.h +++ b/http/http_request.h @@ -9,11 +9,23 @@ #include "common.h" #include "query_string.h" +#if BOOST_VERSION >= 107000 +#include <boost/beast/ssl/ssl_stream.hpp> +#else +#include <boost/beast/experimental/core/ssl_stream.hpp> +#endif + namespace crow { struct Request { +#ifdef BMCWEB_ENABLE_SSL + using Adaptor = boost::beast::ssl_stream<boost::asio::ip::tcp::socket>; +#else + using Adaptor = boost::asio::ip::tcp::socket; +#endif + boost::beast::http::request<boost::beast::http::string_body>& req; boost::beast::http::fields& fields; std::string_view url{}; @@ -28,7 +40,7 @@ struct Request std::shared_ptr<crow::persistent_data::UserSession> session; std::string userRole{}; - + std::function<Adaptor&()> socket; Request( boost::beast::http::request<boost::beast::http::string_body>& reqIn) : req(reqIn), diff --git a/include/dump_offload.hpp b/include/dump_offload.hpp new file mode 100644 index 0000000000..e628ec66f1 --- /dev/null +++ b/include/dump_offload.hpp @@ -0,0 +1,305 @@ +#pragma once + +#include <signal.h> +#include <sys/select.h> + +#include <boost/beast/core/flat_static_buffer.hpp> +#include <boost/beast/http.hpp> +#include <boost/process.hpp> +#include <cstdio> +#include <cstdlib> + +namespace crow +{ +namespace obmc_dump +{ + +inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res, + const std::string& entryId); +inline void resetHandler(); + +// The max network block device buffer size is 128kb plus 16bytes +// for the message header +static constexpr auto nbdBufferSize = 131088; + +/** class Handler + * handles data transfer between nbd-client and nbd-server. + * This handler invokes nbd-proxy and reads data from socket + * and writes on to nbd-client and vice-versa + */ +class Handler : public std::enable_shared_from_this<Handler> +{ + public: + Handler(const std::string& media, boost::asio::io_context& ios, + const std::string& entryID) : + pipeOut(ios), + pipeIn(ios), media(media), entryID(entryID), doingWrite(false), + negotiationDone(false), writeonnbd(false), + outputBuffer(std::make_unique< + boost::beast::flat_static_buffer<nbdBufferSize>>()), + inputBuffer( + std::make_unique<boost::beast::flat_static_buffer<nbdBufferSize>>()) + { + } + + ~Handler() + { + } + + /** + * @brief Invokes InitiateOffload method of dump manager which + * directs pldm to start writing on the nbd device. + * + * @return void + */ + void initiateOffloadOnNbdDevice() + { + crow::connections::systemBus->async_method_call( + [this, + self(shared_from_this())](const boost::system::error_code ec) { + if (ec) + { + BMCWEB_LOG_ERROR << "DBUS response error: " << ec; + resetBuffers(); + resetHandler(); + return; + } + }, + "xyz.openbmc_project.Dump.Manager", + "/xyz/openbmc_project/dump/entry/" + entryID, + "xyz.openbmc_project.Dump.Entry", "InitiateOffload"); + } + + /** + * @brief Kills nbd-proxy + * + * @return void + */ + void doClose() + { + int rc = kill(proxy.id(), SIGTERM); + if (rc) + { + return; + } + proxy.wait(); + } + + /** + * @brief Starts nbd-proxy + * + * @return void + */ + void connect() + { + std::error_code ec; + proxy = boost::process::child("/usr/sbin/nbd-proxy", media, + boost::process::std_out > pipeOut, + boost::process::std_in < pipeIn, ec); + if (ec) + { + BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: " + << ec.message(); + resetHandler(); + return; + } + doRead(); + } + + /** + * @brief Wait for data on tcp socket from nbd-server. + * + * @return void + */ + void waitForMessageOnSocket() + { + + std::size_t bytes = inputBuffer->capacity() - inputBuffer->size(); + + (*stream).async_read_some( + inputBuffer->prepare(bytes), + [this, + self(shared_from_this())](const boost::system::error_code& ec, + std::size_t bytes_transferred) { + if (ec) + { + BMCWEB_LOG_DEBUG << "Error while reading on socket"; + doClose(); + resetBuffers(); + resetHandler(); + return; + } + + inputBuffer->commit(bytes_transferred); + doWrite(); + }); + } + + /** + * @brief Writes data on input pipe of nbd-client. + * + * @return void + */ + void doWrite() + { + + if (doingWrite) + { + BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; + return; + } + + if (inputBuffer->size() == 0) + { + BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out"; + return; + } + + doingWrite = true; + boost::asio::async_write( + pipeIn, inputBuffer->data(), + [this, self(shared_from_this())](const boost::beast::error_code& ec, + std::size_t bytesWritten) { + if (ec) + { + BMCWEB_LOG_DEBUG << "VM socket port closed"; + doClose(); + resetBuffers(); + resetHandler(); + return; + } + + doingWrite = false; + + if (negotiationDone == false) + { + // "gDf" is NBD reply magic + std::string reply_magic("gDf"); + std::string reply_string( + static_cast<char*>(inputBuffer->data().data()), + bytesWritten); + std::size_t found = reply_string.find(reply_magic); + if (found != std::string::npos) + { + negotiationDone = true; + writeonnbd = true; + } + } + + inputBuffer->consume(bytesWritten); + waitForMessageOnSocket(); + if (writeonnbd) + { + // NBD Negotiation Complete!!!!. Notify Dump manager to + // start dumping the actual data over NBD device + initiateOffloadOnNbdDevice(); + writeonnbd = false; + } + }); + } + + /** + * @brief Reads data on output pipe of nbd-client and write on + * tcp socket. + * + * @return void + */ + void doRead() + { + std::size_t bytes = outputBuffer->capacity() - outputBuffer->size(); + + pipeOut.async_read_some( + outputBuffer->prepare(bytes), + [this, self(shared_from_this())]( + const boost::system::error_code& ec, std::size_t bytesRead) { + if (ec) + { + BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec; + doClose(); + resetBuffers(); + resetHandler(); + return; + } + + outputBuffer->commit(bytesRead); + + boost::asio::async_write( + *stream, outputBuffer->data(), + [this](const boost::system::error_code& ec, + std::size_t bytes_transferred) { + if (ec) + { + BMCWEB_LOG_DEBUG << "Error while writing on socket"; + doClose(); + resetBuffers(); + resetHandler(); + return; + } + + outputBuffer->consume(bytes_transferred); + doRead(); + }); + }); + } + + /** + * @brief Resets input and output buffers. + * @return void + */ + void resetBuffers() + { +#if BOOST_VERSION >= 107000 + this->inputBuffer->clear(); + this->outputBuffer->clear(); +#else + this->inputBuffer->reset(); + this->outputBuffer->reset(); +#endif + } + + boost::process::async_pipe pipeOut; + boost::process::async_pipe pipeIn; + boost::process::child proxy; + std::string media; + std::string entryID; + bool doingWrite; + bool negotiationDone; + bool writeonnbd; + std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>> + outputBuffer; + std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>> + inputBuffer; + std::shared_ptr<crow::Request::Adaptor> stream; +}; + +static std::shared_ptr<Handler> handler; +inline void resetHandler() +{ + + handler.reset(); +} +inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res, + const std::string& entryId) +{ + + // Run only one instance of Handler, one dump offload can happen at a time + if (handler != nullptr) + { + BMCWEB_LOG_ERROR << "Handler already running"; + res.result(boost::beast::http::status::service_unavailable); + res.jsonValue["Description"] = "Service is already being used"; + res.end(); + return; + } + + const char* media = "1"; + boost::asio::io_context* io_con = req.ioService; + + handler = std::make_shared<Handler>(media, *io_con, entryId); + handler->stream = + std::make_shared<crow::Request::Adaptor>(std::move(req.socket())); + handler->connect(); + handler->waitForMessageOnSocket(); +} +} // namespace obmc_dump +} // namespace crow |