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