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 <webserver_common.hpp> 9 10 #include <csignal> 11 12 namespace crow 13 { 14 namespace obmc_vm 15 { 16 17 static crow::websocket::Connection* session = nullptr; 18 19 // The max network block device buffer size is 128kb plus 16bytes 20 // for the message header: 21 // https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message 22 static constexpr auto nbdBufferSize = 131088; 23 24 class Handler : public std::enable_shared_from_this<Handler> 25 { 26 public: 27 Handler(const std::string& mediaIn, boost::asio::io_context& ios) : 28 pipeOut(ios), pipeIn(ios), media(mediaIn), doingWrite(false), 29 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>), 30 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>) 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> 159 void requestRoutes(Crow<Middlewares...>& app) 160 { 161 BMCWEB_ROUTE(app, "/vm/0/0") 162 .requires({"ConfigureComponents", "ConfigureManager"}) 163 .websocket() 164 .onopen([](crow::websocket::Connection& conn, 165 std::shared_ptr<bmcweb::AsyncResp> asyncResp) { 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 220