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