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