blob: d07469eba241271aaf00e79d9af2af602006d630 [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>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06008
Gunnar Mills1214b7e2020-06-04 10:11:30 -05009#include <csignal>
10
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060011namespace 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>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050030 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060031
Ed Tanous0c0084a2019-10-24 15:57:51 -070032 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060033
34 void doClose()
35 {
36 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
37 // to allow the proxy to stop nbd-client and the USB device gadget.
38 int rc = kill(proxy.id(), SIGTERM);
39 if (rc)
40 {
41 return;
42 }
43 proxy.wait();
44 }
45
46 void connect()
47 {
48 std::error_code ec;
49 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
50 boost::process::std_out > pipeOut,
51 boost::process::std_in < pipeIn, ec);
52 if (ec)
53 {
54 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
55 << ec.message();
56 if (session != nullptr)
57 {
58 session->close("Error connecting to nbd-proxy");
59 }
60 return;
61 }
62 doWrite();
63 doRead();
64 }
65
66 void doWrite()
67 {
68 if (doingWrite)
69 {
70 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
71 return;
72 }
73
74 if (inputBuffer->size() == 0)
75 {
76 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
77 return;
78 }
79
80 doingWrite = true;
81 pipeIn.async_write_some(
82 inputBuffer->data(),
83 [this, self(shared_from_this())](boost::beast::error_code ec,
84 std::size_t bytesWritten) {
85 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
86 doingWrite = false;
87 inputBuffer->consume(bytesWritten);
88
89 if (session == nullptr)
90 {
91 return;
92 }
93 if (ec == boost::asio::error::eof)
94 {
95 session->close("VM socket port closed");
96 return;
97 }
98 if (ec)
99 {
100 session->close("Error in writing to proxy port");
101 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
102 return;
103 }
104 doWrite();
105 });
106 }
107
108 void doRead()
109 {
110 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
111
112 pipeOut.async_read_some(
113 outputBuffer->prepare(bytes),
114 [this, self(shared_from_this())](
115 const boost::system::error_code& ec, std::size_t bytesRead) {
116 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead
117 << " bytes";
118 if (ec)
119 {
120 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
121 if (session != nullptr)
122 {
123 session->close("Error in connecting to VM port");
124 }
125 return;
126 }
127 if (session == nullptr)
128 {
129 return;
130 }
131
132 outputBuffer->commit(bytesRead);
133 std::string_view payload(
134 static_cast<const char*>(outputBuffer->data().data()),
135 bytesRead);
136 session->sendBinary(payload);
137 outputBuffer->consume(bytesRead);
138
139 doRead();
140 });
141 }
142
143 boost::process::async_pipe pipeOut;
144 boost::process::async_pipe pipeIn;
145 boost::process::child proxy;
146 std::string media;
147 bool doingWrite;
148
149 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
150 outputBuffer;
151 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
152 inputBuffer;
153};
154
155static std::shared_ptr<Handler> handler;
156
Ed Tanous23a21a12020-07-25 04:45:05 +0000157inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600158{
159 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous23a21a12020-07-25 04:45:05 +0000160 .privileges({"ConfigureComponents", "ConfigureManager"})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600161 .websocket()
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200162 .onopen([](crow::websocket::Connection& conn,
163 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600164 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
165
166 if (session != nullptr)
167 {
168 conn.close("Session already connected");
169 return;
170 }
171
172 if (handler != nullptr)
173 {
174 conn.close("Handler already running");
175 return;
176 }
177
178 session = &conn;
179
180 // media is the last digit of the endpoint /vm/0/0. A future
181 // enhancement can include supporting different endpoint values.
182 const char* media = "0";
183 handler = std::make_shared<Handler>(media, conn.get_io_context());
184 handler->connect();
185 })
186 .onclose(
187 [](crow::websocket::Connection& conn, const std::string& reason) {
188 session = nullptr;
189 handler->doClose();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600190 handler->inputBuffer->clear();
191 handler->outputBuffer->clear();
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600192 handler.reset();
193 })
194 .onmessage([](crow::websocket::Connection& conn,
195 const std::string& data, bool is_binary) {
196 if (data.length() > handler->inputBuffer->capacity())
197 {
198 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
199 << data.length() << " bytes";
200 conn.close("Buffer overrun");
201 return;
202 }
203
204 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
205 boost::asio::buffer(data));
206 handler->inputBuffer->commit(data.size());
207 handler->doWrite();
208 });
209}
210
211} // namespace obmc_vm
212} // namespace crow