| Adriana Kobylak | 1bfbe0e | 2019-01-17 12:08:38 -0600 | [diff] [blame] | 1 | #pragma once | 
|  | 2 |  | 
| Ed Tanous | c94ad49 | 2019-10-10 15:39:33 -0700 | [diff] [blame] | 3 | #include <app.h> | 
| Adriana Kobylak | 1bfbe0e | 2019-01-17 12:08:38 -0600 | [diff] [blame] | 4 | #include <signal.h> | 
| Ed Tanous | c94ad49 | 2019-10-10 15:39:33 -0700 | [diff] [blame] | 5 | #include <websocket.h> | 
| Adriana Kobylak | 1bfbe0e | 2019-01-17 12:08:38 -0600 | [diff] [blame] | 6 |  | 
|  | 7 | #include <boost/beast/core/flat_static_buffer.hpp> | 
|  | 8 | #include <boost/process.hpp> | 
|  | 9 | #include <webserver_common.hpp> | 
|  | 10 |  | 
|  | 11 | namespace crow | 
|  | 12 | { | 
|  | 13 | namespace obmc_vm | 
|  | 14 | { | 
|  | 15 |  | 
|  | 16 | static crow::websocket::Connection* session = nullptr; | 
|  | 17 |  | 
|  | 18 | // The max network block device buffer size is 128kb plus 16bytes | 
|  | 19 | // for the message header: | 
|  | 20 | // https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message | 
|  | 21 | static constexpr auto nbdBufferSize = 131088; | 
|  | 22 |  | 
|  | 23 | class Handler : public std::enable_shared_from_this<Handler> | 
|  | 24 | { | 
|  | 25 | public: | 
| Ed Tanous | 271584a | 2019-07-09 16:24:22 -0700 | [diff] [blame] | 26 | Handler(const std::string& mediaIn, boost::asio::io_context& ios) : | 
|  | 27 | pipeOut(ios), pipeIn(ios), media(mediaIn), doingWrite(false), | 
| Adriana Kobylak | 1bfbe0e | 2019-01-17 12:08:38 -0600 | [diff] [blame] | 28 | outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>), | 
|  | 29 | inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>) | 
|  | 30 | { | 
|  | 31 | } | 
|  | 32 |  | 
|  | 33 | ~Handler() | 
|  | 34 | { | 
|  | 35 | } | 
|  | 36 |  | 
|  | 37 | void doClose() | 
|  | 38 | { | 
|  | 39 | // boost::process::child::terminate uses SIGKILL, need to send SIGTERM | 
|  | 40 | // to allow the proxy to stop nbd-client and the USB device gadget. | 
|  | 41 | int rc = kill(proxy.id(), SIGTERM); | 
|  | 42 | if (rc) | 
|  | 43 | { | 
|  | 44 | return; | 
|  | 45 | } | 
|  | 46 | proxy.wait(); | 
|  | 47 | } | 
|  | 48 |  | 
|  | 49 | void connect() | 
|  | 50 | { | 
|  | 51 | std::error_code ec; | 
|  | 52 | proxy = boost::process::child("/usr/sbin/nbd-proxy", media, | 
|  | 53 | boost::process::std_out > pipeOut, | 
|  | 54 | boost::process::std_in < pipeIn, ec); | 
|  | 55 | if (ec) | 
|  | 56 | { | 
|  | 57 | BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: " | 
|  | 58 | << ec.message(); | 
|  | 59 | if (session != nullptr) | 
|  | 60 | { | 
|  | 61 | session->close("Error connecting to nbd-proxy"); | 
|  | 62 | } | 
|  | 63 | return; | 
|  | 64 | } | 
|  | 65 | doWrite(); | 
|  | 66 | doRead(); | 
|  | 67 | } | 
|  | 68 |  | 
|  | 69 | void doWrite() | 
|  | 70 | { | 
|  | 71 | if (doingWrite) | 
|  | 72 | { | 
|  | 73 | BMCWEB_LOG_DEBUG << "Already writing.  Bailing out"; | 
|  | 74 | return; | 
|  | 75 | } | 
|  | 76 |  | 
|  | 77 | if (inputBuffer->size() == 0) | 
|  | 78 | { | 
|  | 79 | BMCWEB_LOG_DEBUG << "inputBuffer empty.  Bailing out"; | 
|  | 80 | return; | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | doingWrite = true; | 
|  | 84 | pipeIn.async_write_some( | 
|  | 85 | inputBuffer->data(), | 
|  | 86 | [this, self(shared_from_this())](boost::beast::error_code ec, | 
|  | 87 | std::size_t bytesWritten) { | 
|  | 88 | BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes"; | 
|  | 89 | doingWrite = false; | 
|  | 90 | inputBuffer->consume(bytesWritten); | 
|  | 91 |  | 
|  | 92 | if (session == nullptr) | 
|  | 93 | { | 
|  | 94 | return; | 
|  | 95 | } | 
|  | 96 | if (ec == boost::asio::error::eof) | 
|  | 97 | { | 
|  | 98 | session->close("VM socket port closed"); | 
|  | 99 | return; | 
|  | 100 | } | 
|  | 101 | if (ec) | 
|  | 102 | { | 
|  | 103 | session->close("Error in writing to proxy port"); | 
|  | 104 | BMCWEB_LOG_ERROR << "Error in VM socket write " << ec; | 
|  | 105 | return; | 
|  | 106 | } | 
|  | 107 | doWrite(); | 
|  | 108 | }); | 
|  | 109 | } | 
|  | 110 |  | 
|  | 111 | void doRead() | 
|  | 112 | { | 
|  | 113 | std::size_t bytes = outputBuffer->capacity() - outputBuffer->size(); | 
|  | 114 |  | 
|  | 115 | pipeOut.async_read_some( | 
|  | 116 | outputBuffer->prepare(bytes), | 
|  | 117 | [this, self(shared_from_this())]( | 
|  | 118 | const boost::system::error_code& ec, std::size_t bytesRead) { | 
|  | 119 | BMCWEB_LOG_DEBUG << "Read done.  Read " << bytesRead | 
|  | 120 | << " bytes"; | 
|  | 121 | if (ec) | 
|  | 122 | { | 
|  | 123 | BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec; | 
|  | 124 | if (session != nullptr) | 
|  | 125 | { | 
|  | 126 | session->close("Error in connecting to VM port"); | 
|  | 127 | } | 
|  | 128 | return; | 
|  | 129 | } | 
|  | 130 | if (session == nullptr) | 
|  | 131 | { | 
|  | 132 | return; | 
|  | 133 | } | 
|  | 134 |  | 
|  | 135 | outputBuffer->commit(bytesRead); | 
|  | 136 | std::string_view payload( | 
|  | 137 | static_cast<const char*>(outputBuffer->data().data()), | 
|  | 138 | bytesRead); | 
|  | 139 | session->sendBinary(payload); | 
|  | 140 | outputBuffer->consume(bytesRead); | 
|  | 141 |  | 
|  | 142 | doRead(); | 
|  | 143 | }); | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | boost::process::async_pipe pipeOut; | 
|  | 147 | boost::process::async_pipe pipeIn; | 
|  | 148 | boost::process::child proxy; | 
|  | 149 | std::string media; | 
|  | 150 | bool doingWrite; | 
|  | 151 |  | 
|  | 152 | std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>> | 
|  | 153 | outputBuffer; | 
|  | 154 | std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>> | 
|  | 155 | inputBuffer; | 
|  | 156 | }; | 
|  | 157 |  | 
|  | 158 | static std::shared_ptr<Handler> handler; | 
|  | 159 |  | 
|  | 160 | template <typename... Middlewares> void requestRoutes(Crow<Middlewares...>& app) | 
|  | 161 | { | 
|  | 162 | BMCWEB_ROUTE(app, "/vm/0/0") | 
| Ed Tanous | 8251ffe | 2019-10-10 14:33:54 -0700 | [diff] [blame] | 163 | .requires({"ConfigureComponents", "ConfigureManager"}) | 
| Adriana Kobylak | 1bfbe0e | 2019-01-17 12:08:38 -0600 | [diff] [blame] | 164 | .websocket() | 
|  | 165 | .onopen([](crow::websocket::Connection& conn) { | 
|  | 166 | BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; | 
|  | 167 |  | 
|  | 168 | if (session != nullptr) | 
|  | 169 | { | 
|  | 170 | conn.close("Session already connected"); | 
|  | 171 | return; | 
|  | 172 | } | 
|  | 173 |  | 
|  | 174 | if (handler != nullptr) | 
|  | 175 | { | 
|  | 176 | conn.close("Handler already running"); | 
|  | 177 | return; | 
|  | 178 | } | 
|  | 179 |  | 
|  | 180 | session = &conn; | 
|  | 181 |  | 
|  | 182 | // media is the last digit of the endpoint /vm/0/0. A future | 
|  | 183 | // enhancement can include supporting different endpoint values. | 
|  | 184 | const char* media = "0"; | 
|  | 185 | handler = std::make_shared<Handler>(media, conn.get_io_context()); | 
|  | 186 | handler->connect(); | 
|  | 187 | }) | 
|  | 188 | .onclose( | 
|  | 189 | [](crow::websocket::Connection& conn, const std::string& reason) { | 
|  | 190 | session = nullptr; | 
|  | 191 | handler->doClose(); | 
|  | 192 | #if BOOST_VERSION >= 107000 | 
|  | 193 | handler->inputBuffer->clear(); | 
|  | 194 | handler->outputBuffer->clear(); | 
|  | 195 | #else | 
|  | 196 | handler->inputBuffer->reset(); | 
|  | 197 | handler->outputBuffer->reset(); | 
|  | 198 | #endif | 
|  | 199 | handler.reset(); | 
|  | 200 | }) | 
|  | 201 | .onmessage([](crow::websocket::Connection& conn, | 
|  | 202 | const std::string& data, bool is_binary) { | 
|  | 203 | if (data.length() > handler->inputBuffer->capacity()) | 
|  | 204 | { | 
|  | 205 | BMCWEB_LOG_ERROR << "Buffer overrun when writing " | 
|  | 206 | << data.length() << " bytes"; | 
|  | 207 | conn.close("Buffer overrun"); | 
|  | 208 | return; | 
|  | 209 | } | 
|  | 210 |  | 
|  | 211 | boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()), | 
|  | 212 | boost::asio::buffer(data)); | 
|  | 213 | handler->inputBuffer->commit(data.size()); | 
|  | 214 | handler->doWrite(); | 
|  | 215 | }); | 
|  | 216 | } | 
|  | 217 |  | 
|  | 218 | } // namespace obmc_vm | 
|  | 219 | } // namespace crow |