blob: ebf0a698e13315a4aa48d24450e85a1430565211 [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
17static 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
Troy Lee4ee8f0b2021-08-02 11:08:26 +080022static constexpr auto nbdBufferSize = (128 * 1024 + 16) * 4;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060023
24class Handler : public std::enable_shared_from_this<Handler>
25{
26 public:
Ed Tanous271584a2019-07-09 16:24:22 -070027 Handler(const std::string& mediaIn, boost::asio::io_context& ios) :
Ed Tanousf5b191a2022-02-15 11:30:39 -080028 pipeOut(ios), pipeIn(ios), media(mediaIn),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060029 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
30 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050031 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060032
Ed Tanous0c0084a2019-10-24 15:57:51 -070033 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060034
Ed Tanousecd6a3a2022-01-07 09:18:40 -080035 Handler(const Handler&) = delete;
36 Handler(Handler&&) = delete;
37 Handler& operator=(const Handler&) = delete;
38 Handler& operator=(Handler&&) = delete;
39
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060040 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);
Ed Tanouse662eae2022-01-25 10:39:19 -080045 if (rc != 0)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060046 {
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) {
Ed Tanous002d39b2022-05-31 08:59:27 -070091 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
92 doingWrite = false;
93 inputBuffer->consume(bytesWritten);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060094
Ed Tanous002d39b2022-05-31 08:59:27 -070095 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();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600111 });
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) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700122 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead << " bytes";
123 if (ec)
124 {
125 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
126 if (session != nullptr)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600127 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700128 session->close("Error in connecting to VM port");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600129 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700130 return;
131 }
132 if (session == nullptr)
133 {
134 return;
135 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600136
Ed Tanous002d39b2022-05-31 08:59:27 -0700137 outputBuffer->commit(bytesRead);
138 std::string_view payload(
139 static_cast<const char*>(outputBuffer->data().data()),
140 bytesRead);
141 session->sendBinary(payload);
142 outputBuffer->consume(bytesRead);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600143
Ed Tanous002d39b2022-05-31 08:59:27 -0700144 doRead();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600145 });
146 }
147
148 boost::process::async_pipe pipeOut;
149 boost::process::async_pipe pipeIn;
150 boost::process::child proxy;
151 std::string media;
Ed Tanousf5b191a2022-02-15 11:30:39 -0800152 bool doingWrite{false};
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600153
154 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
155 outputBuffer;
156 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
157 inputBuffer;
158};
159
160static std::shared_ptr<Handler> handler;
161
Ed Tanous23a21a12020-07-25 04:45:05 +0000162inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600163{
164 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700165 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600166 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800167 .onopen([](crow::websocket::Connection& conn) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600168 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
169
170 if (session != nullptr)
171 {
172 conn.close("Session already connected");
173 return;
174 }
175
176 if (handler != nullptr)
177 {
178 conn.close("Handler already running");
179 return;
180 }
181
182 session = &conn;
183
184 // media is the last digit of the endpoint /vm/0/0. A future
185 // enhancement can include supporting different endpoint values.
186 const char* media = "0";
Ed Tanous2c70f802020-09-28 14:29:23 -0700187 handler = std::make_shared<Handler>(media, conn.getIoContext());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600188 handler->connect();
189 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000190 .onclose([](crow::websocket::Connection& conn,
191 const std::string& /*reason*/) {
192 if (&conn != session)
193 {
194 return;
195 }
196
197 session = nullptr;
198 handler->doClose();
199 handler->inputBuffer->clear();
200 handler->outputBuffer->clear();
201 handler.reset();
202 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600203 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000204 const std::string& data, bool) {
Troy Lee4ee8f0b2021-08-02 11:08:26 +0800205 if (data.length() >
206 handler->inputBuffer->capacity() - handler->inputBuffer->size())
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600207 {
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