blob: 02f958a180e2659bfda60ffb773fe6bc02bfacb3 [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>
5#include <boost/process.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -07006#include <websocket.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06007
Gunnar Mills1214b7e2020-06-04 10:11:30 -05008#include <csignal>
9
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060010namespace crow
11{
12namespace obmc_vm
13{
14
15static crow::websocket::Connection* session = nullptr;
16
17// The max network block device buffer size is 128kb plus 16bytes
18// for the message header:
19// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message
20static constexpr auto nbdBufferSize = 131088;
21
22class Handler : public std::enable_shared_from_this<Handler>
23{
24 public:
Ed Tanous271584a2019-07-09 16:24:22 -070025 Handler(const std::string& mediaIn, boost::asio::io_context& ios) :
26 pipeOut(ios), pipeIn(ios), media(mediaIn), doingWrite(false),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060027 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
28 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050029 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060030
Ed Tanous0c0084a2019-10-24 15:57:51 -070031 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060032
33 void doClose()
34 {
35 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
36 // to allow the proxy to stop nbd-client and the USB device gadget.
37 int rc = kill(proxy.id(), SIGTERM);
38 if (rc)
39 {
40 return;
41 }
42 proxy.wait();
43 }
44
45 void connect()
46 {
47 std::error_code ec;
48 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
49 boost::process::std_out > pipeOut,
50 boost::process::std_in < pipeIn, ec);
51 if (ec)
52 {
53 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
54 << ec.message();
55 if (session != nullptr)
56 {
57 session->close("Error connecting to nbd-proxy");
58 }
59 return;
60 }
61 doWrite();
62 doRead();
63 }
64
65 void doWrite()
66 {
67 if (doingWrite)
68 {
69 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
70 return;
71 }
72
73 if (inputBuffer->size() == 0)
74 {
75 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
76 return;
77 }
78
79 doingWrite = true;
80 pipeIn.async_write_some(
81 inputBuffer->data(),
82 [this, self(shared_from_this())](boost::beast::error_code ec,
83 std::size_t bytesWritten) {
84 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
85 doingWrite = false;
86 inputBuffer->consume(bytesWritten);
87
88 if (session == nullptr)
89 {
90 return;
91 }
92 if (ec == boost::asio::error::eof)
93 {
94 session->close("VM socket port closed");
95 return;
96 }
97 if (ec)
98 {
99 session->close("Error in writing to proxy port");
100 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
101 return;
102 }
103 doWrite();
104 });
105 }
106
107 void doRead()
108 {
109 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
110
111 pipeOut.async_read_some(
112 outputBuffer->prepare(bytes),
113 [this, self(shared_from_this())](
114 const boost::system::error_code& ec, std::size_t bytesRead) {
115 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead
116 << " bytes";
117 if (ec)
118 {
119 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
120 if (session != nullptr)
121 {
122 session->close("Error in connecting to VM port");
123 }
124 return;
125 }
126 if (session == nullptr)
127 {
128 return;
129 }
130
131 outputBuffer->commit(bytesRead);
132 std::string_view payload(
133 static_cast<const char*>(outputBuffer->data().data()),
134 bytesRead);
135 session->sendBinary(payload);
136 outputBuffer->consume(bytesRead);
137
138 doRead();
139 });
140 }
141
142 boost::process::async_pipe pipeOut;
143 boost::process::async_pipe pipeIn;
144 boost::process::child proxy;
145 std::string media;
146 bool doingWrite;
147
148 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
149 outputBuffer;
150 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
151 inputBuffer;
152};
153
154static std::shared_ptr<Handler> handler;
155
Ed Tanous23a21a12020-07-25 04:45:05 +0000156inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600157{
158 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700159 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600160 .websocket()
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200161 .onopen([](crow::websocket::Connection& conn,
Ed Tanousb5a76932020-09-29 16:16:58 -0700162 const std::shared_ptr<bmcweb::AsyncResp>&) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600163 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
164
165 if (session != nullptr)
166 {
167 conn.close("Session already connected");
168 return;
169 }
170
171 if (handler != nullptr)
172 {
173 conn.close("Handler already running");
174 return;
175 }
176
177 session = &conn;
178
179 // media is the last digit of the endpoint /vm/0/0. A future
180 // enhancement can include supporting different endpoint values.
181 const char* media = "0";
Ed Tanous2c70f802020-09-28 14:29:23 -0700182 handler = std::make_shared<Handler>(media, conn.getIoContext());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600183 handler->connect();
184 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000185 .onclose([](crow::websocket::Connection& conn,
186 const std::string& /*reason*/) {
187 if (&conn != session)
188 {
189 return;
190 }
191
192 session = nullptr;
193 handler->doClose();
194 handler->inputBuffer->clear();
195 handler->outputBuffer->clear();
196 handler.reset();
197 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600198 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000199 const std::string& data, bool) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600200 if (data.length() > handler->inputBuffer->capacity())
201 {
202 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
203 << data.length() << " bytes";
204 conn.close("Buffer overrun");
205 return;
206 }
207
208 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
209 boost::asio::buffer(data));
210 handler->inputBuffer->commit(data.size());
211 handler->doWrite();
212 });
213}
214
215} // namespace obmc_vm
216} // namespace crow