blob: 188bcf2ad34ac64d5c5308e0ac54eed899cf15b4 [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"
4
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06005#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanous5edbe942021-10-09 14:49:05 -07006#include <boost/process/async_pipe.hpp>
7#include <boost/process/child.hpp>
8#include <boost/process/io.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -07009#include <websocket.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 {
49 return;
50 }
51 proxy.wait();
52 }
53
54 void connect()
55 {
56 std::error_code ec;
57 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
58 boost::process::std_out > pipeOut,
59 boost::process::std_in < pipeIn, ec);
60 if (ec)
61 {
62 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
63 << ec.message();
64 if (session != nullptr)
65 {
66 session->close("Error connecting to nbd-proxy");
67 }
68 return;
69 }
70 doWrite();
71 doRead();
72 }
73
74 void doWrite()
75 {
76 if (doingWrite)
77 {
78 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
79 return;
80 }
81
82 if (inputBuffer->size() == 0)
83 {
84 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
85 return;
86 }
87
88 doingWrite = true;
89 pipeIn.async_write_some(
90 inputBuffer->data(),
91 [this, self(shared_from_this())](boost::beast::error_code ec,
92 std::size_t bytesWritten) {
Ed Tanous002d39b2022-05-31 08:59:27 -070093 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
94 doingWrite = false;
95 inputBuffer->consume(bytesWritten);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060096
Ed Tanous002d39b2022-05-31 08:59:27 -070097 if (session == nullptr)
98 {
99 return;
100 }
101 if (ec == boost::asio::error::eof)
102 {
103 session->close("VM socket port closed");
104 return;
105 }
106 if (ec)
107 {
108 session->close("Error in writing to proxy port");
109 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
110 return;
111 }
112 doWrite();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600113 });
114 }
115
116 void doRead()
117 {
118 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
119
120 pipeOut.async_read_some(
121 outputBuffer->prepare(bytes),
122 [this, self(shared_from_this())](
123 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700124 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead << " bytes";
125 if (ec)
126 {
127 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
128 if (session != nullptr)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600129 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700130 session->close("Error in connecting to VM port");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600131 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700132 return;
133 }
134 if (session == nullptr)
135 {
136 return;
137 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600138
Ed Tanous002d39b2022-05-31 08:59:27 -0700139 outputBuffer->commit(bytesRead);
140 std::string_view payload(
141 static_cast<const char*>(outputBuffer->data().data()),
142 bytesRead);
143 session->sendBinary(payload);
144 outputBuffer->consume(bytesRead);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600145
Ed Tanous002d39b2022-05-31 08:59:27 -0700146 doRead();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600147 });
148 }
149
150 boost::process::async_pipe pipeOut;
151 boost::process::async_pipe pipeIn;
152 boost::process::child proxy;
153 std::string media;
Ed Tanousf5b191a2022-02-15 11:30:39 -0800154 bool doingWrite{false};
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600155
156 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
157 outputBuffer;
158 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
159 inputBuffer;
160};
161
Ed Tanouscf9e4172022-12-21 09:30:16 -0800162// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600163static std::shared_ptr<Handler> handler;
164
Ed Tanous23a21a12020-07-25 04:45:05 +0000165inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600166{
167 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700168 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600169 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800170 .onopen([](crow::websocket::Connection& conn) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600171 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
172
173 if (session != nullptr)
174 {
175 conn.close("Session already connected");
176 return;
177 }
178
179 if (handler != nullptr)
180 {
181 conn.close("Handler already running");
182 return;
183 }
184
185 session = &conn;
186
187 // media is the last digit of the endpoint /vm/0/0. A future
188 // enhancement can include supporting different endpoint values.
189 const char* media = "0";
Ed Tanous2c70f802020-09-28 14:29:23 -0700190 handler = std::make_shared<Handler>(media, conn.getIoContext());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600191 handler->connect();
192 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000193 .onclose([](crow::websocket::Connection& conn,
194 const std::string& /*reason*/) {
195 if (&conn != session)
196 {
197 return;
198 }
199
200 session = nullptr;
201 handler->doClose();
202 handler->inputBuffer->clear();
203 handler->outputBuffer->clear();
204 handler.reset();
205 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600206 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000207 const std::string& data, bool) {
Troy Lee4ee8f0b2021-08-02 11:08:26 +0800208 if (data.length() >
209 handler->inputBuffer->capacity() - handler->inputBuffer->size())
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600210 {
211 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
212 << data.length() << " bytes";
213 conn.close("Buffer overrun");
214 return;
215 }
216
217 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
218 boost::asio::buffer(data));
219 handler->inputBuffer->commit(data.size());
220 handler->doWrite();
221 });
222}
223
224} // namespace obmc_vm
225} // namespace crow