blob: 1d3bf96c4c95e9e841f205b284845d4182f19515 [file] [log] [blame]
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06001#pragma once
2
Ed Tanous04e438c2020-10-03 08:06:26 -07003#include <app.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06004#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanous5edbe942021-10-09 14:49:05 -07005#include <boost/process/async_pipe.hpp>
6#include <boost/process/child.hpp>
7#include <boost/process/io.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -07008#include <websocket.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06009
Gunnar Mills1214b7e2020-06-04 10:11:30 -050010#include <csignal>
11
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060012namespace crow
13{
14namespace obmc_vm
15{
16
Ed Tanouscf9e4172022-12-21 09:30:16 -080017// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060018static crow::websocket::Connection* session = nullptr;
19
20// The max network block device buffer size is 128kb plus 16bytes
21// for the message header:
22// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message
Troy Lee4ee8f0b2021-08-02 11:08:26 +080023static constexpr auto nbdBufferSize = (128 * 1024 + 16) * 4;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060024
25class Handler : public std::enable_shared_from_this<Handler>
26{
27 public:
Ed Tanous271584a2019-07-09 16:24:22 -070028 Handler(const std::string& mediaIn, boost::asio::io_context& ios) :
Ed Tanousf5b191a2022-02-15 11:30:39 -080029 pipeOut(ios), pipeIn(ios), media(mediaIn),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060030 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
31 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050032 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060033
Ed Tanous0c0084a2019-10-24 15:57:51 -070034 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060035
Ed Tanousecd6a3a2022-01-07 09:18:40 -080036 Handler(const Handler&) = delete;
37 Handler(Handler&&) = delete;
38 Handler& operator=(const Handler&) = delete;
39 Handler& operator=(Handler&&) = delete;
40
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060041 void doClose()
42 {
43 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
44 // to allow the proxy to stop nbd-client and the USB device gadget.
45 int rc = kill(proxy.id(), SIGTERM);
Ed Tanouse662eae2022-01-25 10:39:19 -080046 if (rc != 0)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060047 {
48 return;
49 }
50 proxy.wait();
51 }
52
53 void connect()
54 {
55 std::error_code ec;
56 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
57 boost::process::std_out > pipeOut,
58 boost::process::std_in < pipeIn, ec);
59 if (ec)
60 {
61 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
62 << ec.message();
63 if (session != nullptr)
64 {
65 session->close("Error connecting to nbd-proxy");
66 }
67 return;
68 }
69 doWrite();
70 doRead();
71 }
72
73 void doWrite()
74 {
75 if (doingWrite)
76 {
77 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
78 return;
79 }
80
81 if (inputBuffer->size() == 0)
82 {
83 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
84 return;
85 }
86
87 doingWrite = true;
88 pipeIn.async_write_some(
89 inputBuffer->data(),
90 [this, self(shared_from_this())](boost::beast::error_code ec,
91 std::size_t bytesWritten) {
Ed Tanous002d39b2022-05-31 08:59:27 -070092 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
93 doingWrite = false;
94 inputBuffer->consume(bytesWritten);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060095
Ed Tanous002d39b2022-05-31 08:59:27 -070096 if (session == nullptr)
97 {
98 return;
99 }
100 if (ec == boost::asio::error::eof)
101 {
102 session->close("VM socket port closed");
103 return;
104 }
105 if (ec)
106 {
107 session->close("Error in writing to proxy port");
108 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
109 return;
110 }
111 doWrite();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600112 });
113 }
114
115 void doRead()
116 {
117 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
118
119 pipeOut.async_read_some(
120 outputBuffer->prepare(bytes),
121 [this, self(shared_from_this())](
122 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700123 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead << " bytes";
124 if (ec)
125 {
126 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
127 if (session != nullptr)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600128 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700129 session->close("Error in connecting to VM port");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600130 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700131 return;
132 }
133 if (session == nullptr)
134 {
135 return;
136 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600137
Ed Tanous002d39b2022-05-31 08:59:27 -0700138 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);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600144
Ed Tanous002d39b2022-05-31 08:59:27 -0700145 doRead();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600146 });
147 }
148
149 boost::process::async_pipe pipeOut;
150 boost::process::async_pipe pipeIn;
151 boost::process::child proxy;
152 std::string media;
Ed Tanousf5b191a2022-02-15 11:30:39 -0800153 bool doingWrite{false};
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600154
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
Ed Tanouscf9e4172022-12-21 09:30:16 -0800161// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600162static std::shared_ptr<Handler> handler;
163
Ed Tanous23a21a12020-07-25 04:45:05 +0000164inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600165{
166 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700167 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600168 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800169 .onopen([](crow::websocket::Connection& conn) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600170 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
171
172 if (session != nullptr)
173 {
174 conn.close("Session already connected");
175 return;
176 }
177
178 if (handler != nullptr)
179 {
180 conn.close("Handler already running");
181 return;
182 }
183
184 session = &conn;
185
186 // media is the last digit of the endpoint /vm/0/0. A future
187 // enhancement can include supporting different endpoint values.
188 const char* media = "0";
Ed Tanous2c70f802020-09-28 14:29:23 -0700189 handler = std::make_shared<Handler>(media, conn.getIoContext());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600190 handler->connect();
191 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000192 .onclose([](crow::websocket::Connection& conn,
193 const std::string& /*reason*/) {
194 if (&conn != session)
195 {
196 return;
197 }
198
199 session = nullptr;
200 handler->doClose();
201 handler->inputBuffer->clear();
202 handler->outputBuffer->clear();
203 handler.reset();
204 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600205 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000206 const std::string& data, bool) {
Troy Lee4ee8f0b2021-08-02 11:08:26 +0800207 if (data.length() >
208 handler->inputBuffer->capacity() - handler->inputBuffer->size())
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600209 {
210 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
211 << data.length() << " bytes";
212 conn.close("Buffer overrun");
213 return;
214 }
215
216 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
217 boost::asio::buffer(data));
218 handler->inputBuffer->commit(data.size());
219 handler->doWrite();
220 });
221}
222
223} // namespace obmc_vm
224} // namespace crow