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