blob: 13bbdc6b2353486859db824c1e87028684327ecc [file] [log] [blame]
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06001#pragma once
2
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08003#include "app.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07004#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06006#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanous5edbe942021-10-09 14:49:05 -07007#include <boost/process/async_pipe.hpp>
8#include <boost/process/child.hpp>
9#include <boost/process/io.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060010
Gunnar Mills1214b7e2020-06-04 10:11:30 -050011#include <csignal>
12
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060013namespace crow
14{
15namespace obmc_vm
16{
17
Ed Tanouscf9e4172022-12-21 09:30:16 -080018// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060019static crow::websocket::Connection* session = nullptr;
20
21// The max network block device buffer size is 128kb plus 16bytes
22// for the message header:
23// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message
Troy Lee4ee8f0b2021-08-02 11:08:26 +080024static constexpr auto nbdBufferSize = (128 * 1024 + 16) * 4;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060025
26class Handler : public std::enable_shared_from_this<Handler>
27{
28 public:
Ed Tanous271584a2019-07-09 16:24:22 -070029 Handler(const std::string& mediaIn, boost::asio::io_context& ios) :
Ed Tanousf5b191a2022-02-15 11:30:39 -080030 pipeOut(ios), pipeIn(ios), media(mediaIn),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060031 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
32 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050033 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060034
Ed Tanous0c0084a2019-10-24 15:57:51 -070035 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060036
Ed Tanousecd6a3a2022-01-07 09:18:40 -080037 Handler(const Handler&) = delete;
38 Handler(Handler&&) = delete;
39 Handler& operator=(const Handler&) = delete;
40 Handler& operator=(Handler&&) = delete;
41
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060042 void doClose()
43 {
44 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
45 // to allow the proxy to stop nbd-client and the USB device gadget.
46 int rc = kill(proxy.id(), SIGTERM);
Ed Tanouse662eae2022-01-25 10:39:19 -080047 if (rc != 0)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060048 {
Ed Tanous62598e32023-07-17 17:06:25 -070049 BMCWEB_LOG_ERROR("Failed to terminate nbd-proxy: {}", errno);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060050 return;
51 }
Troy Lee36ecbf32021-08-17 18:15:28 +080052
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060053 proxy.wait();
54 }
55
56 void connect()
57 {
58 std::error_code ec;
Troy Leee1bf8bb2023-09-08 11:49:58 +080059 proxy = boost::process::child("/usr/bin/nbd-proxy", media,
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060060 boost::process::std_out > pipeOut,
61 boost::process::std_in < pipeIn, ec);
62 if (ec)
63 {
Ed Tanous62598e32023-07-17 17:06:25 -070064 BMCWEB_LOG_ERROR("Couldn't connect to nbd-proxy: {}", ec.message());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060065 if (session != nullptr)
66 {
67 session->close("Error connecting to nbd-proxy");
68 }
69 return;
70 }
71 doWrite();
72 doRead();
73 }
74
75 void doWrite()
76 {
77 if (doingWrite)
78 {
Ed Tanous62598e32023-07-17 17:06:25 -070079 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060080 return;
81 }
82
83 if (inputBuffer->size() == 0)
84 {
Ed Tanous62598e32023-07-17 17:06:25 -070085 BMCWEB_LOG_DEBUG("inputBuffer empty. Bailing out");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060086 return;
87 }
88
89 doingWrite = true;
90 pipeIn.async_write_some(
91 inputBuffer->data(),
Ed Tanous81c4e332023-05-18 10:30:34 -070092 [this, self(shared_from_this())](const boost::beast::error_code& ec,
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060093 std::size_t bytesWritten) {
Ed Tanous62598e32023-07-17 17:06:25 -070094 BMCWEB_LOG_DEBUG("Wrote {}bytes", bytesWritten);
Ed Tanous002d39b2022-05-31 08:59:27 -070095 doingWrite = false;
96 inputBuffer->consume(bytesWritten);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060097
Ed Tanous002d39b2022-05-31 08:59:27 -070098 if (session == nullptr)
99 {
100 return;
101 }
102 if (ec == boost::asio::error::eof)
103 {
104 session->close("VM socket port closed");
105 return;
106 }
107 if (ec)
108 {
109 session->close("Error in writing to proxy port");
Ed Tanous62598e32023-07-17 17:06:25 -0700110 BMCWEB_LOG_ERROR("Error in VM socket write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700111 return;
112 }
113 doWrite();
Patrick Williams5a39f772023-10-20 11:20:21 -0500114 });
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600115 }
116
117 void doRead()
118 {
119 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
120
121 pipeOut.async_read_some(
122 outputBuffer->prepare(bytes),
123 [this, self(shared_from_this())](
124 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous62598e32023-07-17 17:06:25 -0700125 BMCWEB_LOG_DEBUG("Read done. Read {} bytes", bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700126 if (ec)
127 {
Ed Tanous62598e32023-07-17 17:06:25 -0700128 BMCWEB_LOG_ERROR("Couldn't read from VM port: {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700129 if (session != nullptr)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600130 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700131 session->close("Error in connecting to VM port");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600132 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700133 return;
134 }
135 if (session == nullptr)
136 {
137 return;
138 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600139
Ed Tanous002d39b2022-05-31 08:59:27 -0700140 outputBuffer->commit(bytesRead);
141 std::string_view payload(
142 static_cast<const char*>(outputBuffer->data().data()),
143 bytesRead);
144 session->sendBinary(payload);
145 outputBuffer->consume(bytesRead);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600146
Ed Tanous002d39b2022-05-31 08:59:27 -0700147 doRead();
Patrick Williams5a39f772023-10-20 11:20:21 -0500148 });
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600149 }
150
151 boost::process::async_pipe pipeOut;
152 boost::process::async_pipe pipeIn;
153 boost::process::child proxy;
154 std::string media;
Ed Tanousf5b191a2022-02-15 11:30:39 -0800155 bool doingWrite{false};
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600156
157 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
158 outputBuffer;
159 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
160 inputBuffer;
161};
162
Ed Tanouscf9e4172022-12-21 09:30:16 -0800163// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600164static std::shared_ptr<Handler> handler;
165
Ed Tanous23a21a12020-07-25 04:45:05 +0000166inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600167{
168 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700169 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600170 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800171 .onopen([](crow::websocket::Connection& conn) {
Patrick Williams5a39f772023-10-20 11:20:21 -0500172 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600173
Patrick Williams5a39f772023-10-20 11:20:21 -0500174 if (session != nullptr)
175 {
176 conn.close("Session already connected");
177 return;
178 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600179
Patrick Williams5a39f772023-10-20 11:20:21 -0500180 if (handler != nullptr)
181 {
182 conn.close("Handler already running");
183 return;
184 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600185
Patrick Williams5a39f772023-10-20 11:20:21 -0500186 session = &conn;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600187
Patrick Williams5a39f772023-10-20 11:20:21 -0500188 // media is the last digit of the endpoint /vm/0/0. A future
189 // enhancement can include supporting different endpoint values.
190 const char* media = "0";
191 handler = std::make_shared<Handler>(media, conn.getIoContext());
192 handler->connect();
193 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000194 .onclose([](crow::websocket::Connection& conn,
195 const std::string& /*reason*/) {
Patrick Williams5a39f772023-10-20 11:20:21 -0500196 if (&conn != session)
197 {
198 return;
199 }
Ed Tanouscb13a392020-07-25 19:02:03 +0000200
Patrick Williams5a39f772023-10-20 11:20:21 -0500201 session = nullptr;
202 handler->doClose();
203 handler->inputBuffer->clear();
204 handler->outputBuffer->clear();
205 handler.reset();
206 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600207 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000208 const std::string& data, bool) {
Patrick Williams5a39f772023-10-20 11:20:21 -0500209 if (data.length() >
210 handler->inputBuffer->capacity() - handler->inputBuffer->size())
211 {
212 BMCWEB_LOG_ERROR("Buffer overrun when writing {} bytes",
213 data.length());
214 conn.close("Buffer overrun");
215 return;
216 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600217
Patrick Williams5a39f772023-10-20 11:20:21 -0500218 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
219 boost::asio::buffer(data));
220 handler->inputBuffer->commit(data.size());
221 handler->doWrite();
222 });
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600223}
224
225} // namespace obmc_vm
226} // namespace crow