blob: 6b7d9e7a9f357211058773f5b442bc0c968c4afc [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
22static constexpr auto nbdBufferSize = 131088;
23
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) :
28 pipeOut(ios), pipeIn(ios), media(mediaIn), doingWrite(false),
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) {
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;
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
161static std::shared_ptr<Handler> handler;
162
Ed Tanous23a21a12020-07-25 04:45:05 +0000163inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600164{
165 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700166 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600167 .websocket()
Gunnar Millsccd584f2021-11-16 11:36:33 -0600168 .onopen([](crow::websocket::Connection& conn,
169 const std::shared_ptr<bmcweb::AsyncResp>&) {
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) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600207 if (data.length() > handler->inputBuffer->capacity())
208 {
209 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
210 << data.length() << " bytes";
211 conn.close("Buffer overrun");
212 return;
213 }
214
215 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
216 boost::asio::buffer(data));
217 handler->inputBuffer->commit(data.size());
218 handler->doWrite();
219 });
220}
221
222} // namespace obmc_vm
223} // namespace crow