blob: a8380a735e8ff261bb42f20401a5468a1ca086b3 [file] [log] [blame]
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06001#pragma once
2
Ed Tanousc94ad492019-10-10 15:39:33 -07003#include <app.h>
Ed Tanousc94ad492019-10-10 15:39:33 -07004#include <websocket.h>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06005
6#include <boost/beast/core/flat_static_buffer.hpp>
7#include <boost/process.hpp>
Ed Tanous9e27a222019-10-24 13:46:39 -07008#include <csignal>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06009#include <webserver_common.hpp>
10
11namespace crow
12{
13namespace obmc_vm
14{
15
16static crow::websocket::Connection* session = nullptr;
17
18// The max network block device buffer size is 128kb plus 16bytes
19// for the message header:
20// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message
21static constexpr auto nbdBufferSize = 131088;
22
23class Handler : public std::enable_shared_from_this<Handler>
24{
25 public:
Ed Tanous271584a2019-07-09 16:24:22 -070026 Handler(const std::string& mediaIn, boost::asio::io_context& ios) :
27 pipeOut(ios), pipeIn(ios), media(mediaIn), doingWrite(false),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060028 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
29 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
30 {
31 }
32
Ed Tanous0c0084a2019-10-24 15:57:51 -070033 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060034
35 void doClose()
36 {
37 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
38 // to allow the proxy to stop nbd-client and the USB device gadget.
39 int rc = kill(proxy.id(), SIGTERM);
40 if (rc)
41 {
42 return;
43 }
44 proxy.wait();
45 }
46
47 void connect()
48 {
49 std::error_code ec;
50 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
51 boost::process::std_out > pipeOut,
52 boost::process::std_in < pipeIn, ec);
53 if (ec)
54 {
55 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
56 << ec.message();
57 if (session != nullptr)
58 {
59 session->close("Error connecting to nbd-proxy");
60 }
61 return;
62 }
63 doWrite();
64 doRead();
65 }
66
67 void doWrite()
68 {
69 if (doingWrite)
70 {
71 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
72 return;
73 }
74
75 if (inputBuffer->size() == 0)
76 {
77 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
78 return;
79 }
80
81 doingWrite = true;
82 pipeIn.async_write_some(
83 inputBuffer->data(),
84 [this, self(shared_from_this())](boost::beast::error_code ec,
85 std::size_t bytesWritten) {
86 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
87 doingWrite = false;
88 inputBuffer->consume(bytesWritten);
89
90 if (session == nullptr)
91 {
92 return;
93 }
94 if (ec == boost::asio::error::eof)
95 {
96 session->close("VM socket port closed");
97 return;
98 }
99 if (ec)
100 {
101 session->close("Error in writing to proxy port");
102 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
103 return;
104 }
105 doWrite();
106 });
107 }
108
109 void doRead()
110 {
111 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
112
113 pipeOut.async_read_some(
114 outputBuffer->prepare(bytes),
115 [this, self(shared_from_this())](
116 const boost::system::error_code& ec, std::size_t bytesRead) {
117 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead
118 << " bytes";
119 if (ec)
120 {
121 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
122 if (session != nullptr)
123 {
124 session->close("Error in connecting to VM port");
125 }
126 return;
127 }
128 if (session == nullptr)
129 {
130 return;
131 }
132
133 outputBuffer->commit(bytesRead);
134 std::string_view payload(
135 static_cast<const char*>(outputBuffer->data().data()),
136 bytesRead);
137 session->sendBinary(payload);
138 outputBuffer->consume(bytesRead);
139
140 doRead();
141 });
142 }
143
144 boost::process::async_pipe pipeOut;
145 boost::process::async_pipe pipeIn;
146 boost::process::child proxy;
147 std::string media;
148 bool doingWrite;
149
150 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
151 outputBuffer;
152 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
153 inputBuffer;
154};
155
156static std::shared_ptr<Handler> handler;
157
158template <typename... Middlewares> void requestRoutes(Crow<Middlewares...>& app)
159{
160 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous8251ffe2019-10-10 14:33:54 -0700161 .requires({"ConfigureComponents", "ConfigureManager"})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600162 .websocket()
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200163 .onopen([](crow::websocket::Connection& conn,
164 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600165 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
166
167 if (session != nullptr)
168 {
169 conn.close("Session already connected");
170 return;
171 }
172
173 if (handler != nullptr)
174 {
175 conn.close("Handler already running");
176 return;
177 }
178
179 session = &conn;
180
181 // media is the last digit of the endpoint /vm/0/0. A future
182 // enhancement can include supporting different endpoint values.
183 const char* media = "0";
184 handler = std::make_shared<Handler>(media, conn.get_io_context());
185 handler->connect();
186 })
187 .onclose(
188 [](crow::websocket::Connection& conn, const std::string& reason) {
189 session = nullptr;
190 handler->doClose();
191#if BOOST_VERSION >= 107000
192 handler->inputBuffer->clear();
193 handler->outputBuffer->clear();
194#else
195 handler->inputBuffer->reset();
196 handler->outputBuffer->reset();
197#endif
198 handler.reset();
199 })
200 .onmessage([](crow::websocket::Connection& conn,
201 const std::string& data, bool is_binary) {
202 if (data.length() > handler->inputBuffer->capacity())
203 {
204 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
205 << data.length() << " bytes";
206 conn.close("Buffer overrun");
207 return;
208 }
209
210 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
211 boost::asio::buffer(data));
212 handler->inputBuffer->commit(data.size());
213 handler->doWrite();
214 });
215}
216
217} // namespace obmc_vm
218} // namespace crow