blob: 3f229e654293a4c4922fe5fc29ed4383c2107d26 [file] [log] [blame]
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06001#pragma once
2
3#include <crow/app.h>
4#include <crow/websocket.h>
5#include <signal.h>
6
7#include <boost/beast/core/flat_static_buffer.hpp>
8#include <boost/process.hpp>
9#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:
26 Handler(const std::string& media, boost::asio::io_service& ios) :
27 pipeOut(ios), pipeIn(ios), media(media), doingWrite(false),
28 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
29 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
30 {
31 }
32
33 ~Handler()
34 {
35 }
36
37 void doClose()
38 {
39 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
40 // to allow the proxy to stop nbd-client and the USB device gadget.
41 int rc = kill(proxy.id(), SIGTERM);
42 if (rc)
43 {
44 return;
45 }
46 proxy.wait();
47 }
48
49 void connect()
50 {
51 std::error_code ec;
52 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
53 boost::process::std_out > pipeOut,
54 boost::process::std_in < pipeIn, ec);
55 if (ec)
56 {
57 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
58 << ec.message();
59 if (session != nullptr)
60 {
61 session->close("Error connecting to nbd-proxy");
62 }
63 return;
64 }
65 doWrite();
66 doRead();
67 }
68
69 void doWrite()
70 {
71 if (doingWrite)
72 {
73 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
74 return;
75 }
76
77 if (inputBuffer->size() == 0)
78 {
79 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
80 return;
81 }
82
83 doingWrite = true;
84 pipeIn.async_write_some(
85 inputBuffer->data(),
86 [this, self(shared_from_this())](boost::beast::error_code ec,
87 std::size_t bytesWritten) {
88 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
89 doingWrite = false;
90 inputBuffer->consume(bytesWritten);
91
92 if (session == nullptr)
93 {
94 return;
95 }
96 if (ec == boost::asio::error::eof)
97 {
98 session->close("VM socket port closed");
99 return;
100 }
101 if (ec)
102 {
103 session->close("Error in writing to proxy port");
104 BMCWEB_LOG_ERROR << "Error in VM socket write " << ec;
105 return;
106 }
107 doWrite();
108 });
109 }
110
111 void doRead()
112 {
113 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
114
115 pipeOut.async_read_some(
116 outputBuffer->prepare(bytes),
117 [this, self(shared_from_this())](
118 const boost::system::error_code& ec, std::size_t bytesRead) {
119 BMCWEB_LOG_DEBUG << "Read done. Read " << bytesRead
120 << " bytes";
121 if (ec)
122 {
123 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
124 if (session != nullptr)
125 {
126 session->close("Error in connecting to VM port");
127 }
128 return;
129 }
130 if (session == nullptr)
131 {
132 return;
133 }
134
135 outputBuffer->commit(bytesRead);
136 std::string_view payload(
137 static_cast<const char*>(outputBuffer->data().data()),
138 bytesRead);
139 session->sendBinary(payload);
140 outputBuffer->consume(bytesRead);
141
142 doRead();
143 });
144 }
145
146 boost::process::async_pipe pipeOut;
147 boost::process::async_pipe pipeIn;
148 boost::process::child proxy;
149 std::string media;
150 bool doingWrite;
151
152 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
153 outputBuffer;
154 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
155 inputBuffer;
156};
157
158static std::shared_ptr<Handler> handler;
159
160template <typename... Middlewares> void requestRoutes(Crow<Middlewares...>& app)
161{
162 BMCWEB_ROUTE(app, "/vm/0/0")
163 .websocket()
164 .onopen([](crow::websocket::Connection& conn) {
165 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