blob: 1e1b7e13d123b35035f9e15e7331f13bd73d300e [file] [log] [blame]
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06001#pragma once
2
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08003#include "app.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07004#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06006#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanous5edbe942021-10-09 14:49:05 -07007#include <boost/process/async_pipe.hpp>
8#include <boost/process/child.hpp>
9#include <boost/process/io.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060010
Gunnar Mills1214b7e2020-06-04 10:11:30 -050011#include <csignal>
12
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060013namespace crow
14{
15namespace obmc_vm
16{
17
Ed Tanouscf9e4172022-12-21 09:30:16 -080018// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060019static crow::websocket::Connection* session = nullptr;
20
21// The max network block device buffer size is 128kb plus 16bytes
22// for the message header:
23// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message
Troy Lee4ee8f0b2021-08-02 11:08:26 +080024static constexpr auto nbdBufferSize = (128 * 1024 + 16) * 4;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060025
26class Handler : public std::enable_shared_from_this<Handler>
27{
28 public:
Ed Tanous271584a2019-07-09 16:24:22 -070029 Handler(const std::string& mediaIn, boost::asio::io_context& ios) :
Ed Tanousf5b191a2022-02-15 11:30:39 -080030 pipeOut(ios), pipeIn(ios), media(mediaIn),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060031 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
32 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050033 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060034
Ed Tanous0c0084a2019-10-24 15:57:51 -070035 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060036
Ed Tanousecd6a3a2022-01-07 09:18:40 -080037 Handler(const Handler&) = delete;
38 Handler(Handler&&) = delete;
39 Handler& operator=(const Handler&) = delete;
40 Handler& operator=(Handler&&) = delete;
41
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060042 void doClose()
43 {
44 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
45 // to allow the proxy to stop nbd-client and the USB device gadget.
46 int rc = kill(proxy.id(), SIGTERM);
Ed Tanouse662eae2022-01-25 10:39:19 -080047 if (rc != 0)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060048 {
Troy Lee36ecbf32021-08-17 18:15:28 +080049 BMCWEB_LOG_ERROR << "Failed to terminate nbd-proxy: " << errno;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060050 return;
51 }
Troy Lee36ecbf32021-08-17 18:15:28 +080052
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060053 proxy.wait();
54 }
55
56 void connect()
57 {
58 std::error_code ec;
59 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
60 boost::process::std_out > pipeOut,
61 boost::process::std_in < pipeIn, ec);
62 if (ec)
63 {
64 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
65 << ec.message();
66 if (session != nullptr)
67 {
68 session->close("Error connecting to nbd-proxy");
69 }
70 return;
71 }
72 doWrite();
73 doRead();
74 }
75
76 void doWrite()
77 {
78 if (doingWrite)
79 {
80 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
81 return;
82 }
83
84 if (inputBuffer->size() == 0)
85 {
86 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
87 return;
88 }
89
90 doingWrite = true;
91 pipeIn.async_write_some(
92 inputBuffer->data(),
Ed Tanous81c4e332023-05-18 10:30:34 -070093 [this, self(shared_from_this())](const boost::beast::error_code& ec,
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060094 std::size_t bytesWritten) {
Ed Tanous002d39b2022-05-31 08:59:27 -070095 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
96 doingWrite = false;
97 inputBuffer->consume(bytesWritten);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060098
Ed Tanous002d39b2022-05-31 08:59:27 -070099 if (session == nullptr)
100 {
101 return;
102 }
103 if (ec == boost::asio::error::eof)
104 {
105 session->close("VM socket port closed");
106 return;
107 }
108 if (ec)
109 {
110 session->close("Error in writing to proxy port");
111 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
112 return;
113 }
114 doWrite();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600115 });
116 }
117
118 void doRead()
119 {
120 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
121
122 pipeOut.async_read_some(
123 outputBuffer->prepare(bytes),
124 [this, self(shared_from_this())](
125 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700126 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead << " bytes";
127 if (ec)
128 {
129 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
130 if (session != nullptr)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600131 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700132 session->close("Error in connecting to VM port");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600133 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700134 return;
135 }
136 if (session == nullptr)
137 {
138 return;
139 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600140
Ed Tanous002d39b2022-05-31 08:59:27 -0700141 outputBuffer->commit(bytesRead);
142 std::string_view payload(
143 static_cast<const char*>(outputBuffer->data().data()),
144 bytesRead);
145 session->sendBinary(payload);
146 outputBuffer->consume(bytesRead);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600147
Ed Tanous002d39b2022-05-31 08:59:27 -0700148 doRead();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600149 });
150 }
151
152 boost::process::async_pipe pipeOut;
153 boost::process::async_pipe pipeIn;
154 boost::process::child proxy;
155 std::string media;
Ed Tanousf5b191a2022-02-15 11:30:39 -0800156 bool doingWrite{false};
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600157
158 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
159 outputBuffer;
160 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
161 inputBuffer;
162};
163
Ed Tanouscf9e4172022-12-21 09:30:16 -0800164// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600165static std::shared_ptr<Handler> handler;
166
Ed Tanous23a21a12020-07-25 04:45:05 +0000167inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600168{
169 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700170 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600171 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800172 .onopen([](crow::websocket::Connection& conn) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600173 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
174
175 if (session != nullptr)
176 {
177 conn.close("Session already connected");
178 return;
179 }
180
181 if (handler != nullptr)
182 {
183 conn.close("Handler already running");
184 return;
185 }
186
187 session = &conn;
188
189 // media is the last digit of the endpoint /vm/0/0. A future
190 // enhancement can include supporting different endpoint values.
191 const char* media = "0";
Ed Tanous2c70f802020-09-28 14:29:23 -0700192 handler = std::make_shared<Handler>(media, conn.getIoContext());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600193 handler->connect();
194 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000195 .onclose([](crow::websocket::Connection& conn,
196 const std::string& /*reason*/) {
197 if (&conn != session)
198 {
199 return;
200 }
201
202 session = nullptr;
203 handler->doClose();
204 handler->inputBuffer->clear();
205 handler->outputBuffer->clear();
206 handler.reset();
207 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600208 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000209 const std::string& data, bool) {
Troy Lee4ee8f0b2021-08-02 11:08:26 +0800210 if (data.length() >
211 handler->inputBuffer->capacity() - handler->inputBuffer->size())
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600212 {
213 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
214 << data.length() << " bytes";
215 conn.close("Buffer overrun");
216 return;
217 }
218
219 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
220 boost::asio::buffer(data));
221 handler->inputBuffer->commit(data.size());
222 handler->doWrite();
223 });
224}
225
226} // namespace obmc_vm
227} // namespace crow