blob: 6922ae276583369fdec019d63d90ac87c4c9acd9 [file] [log] [blame]
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +02001/*
2// Copyright (c) 2019 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
17#include <app.h>
18#include <websocket.h>
19
20#include <boost/asio.hpp>
21#include <boost/beast/core/buffers_to_string.hpp>
22#include <boost/beast/core/multi_buffer.hpp>
23#include <boost/container/flat_map.hpp>
24#include <dbus_utility.hpp>
25#include <experimental/filesystem>
26#include <variant>
27#include <webserver_common.hpp>
28
29namespace crow
30{
31
32namespace nbd_proxy
33{
34
35using boost::asio::local::stream_protocol;
36
37static constexpr auto nbdBufferSize = 131088;
38
39struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
40{
41 NbdProxyServer(crow::websocket::Connection& connIn,
42 const std::string& socketIdIn,
43 const std::string& endpointIdIn, const std::string& pathIn) :
44 socketId(socketIdIn),
45 endpointId(endpointIdIn), path(pathIn),
46 acceptor(connIn.get_io_context(), stream_protocol::endpoint(socketId)),
47 connection(connIn)
48 {
49 }
50
51 ~NbdProxyServer()
52 {
53 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
54 close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020055 }
56
57 std::string getEndpointId() const
58 {
59 return endpointId;
60 }
61
62 void run()
63 {
64 acceptor.async_accept(
65 [this, self(shared_from_this())](boost::system::error_code ec,
66 stream_protocol::socket socket) {
67 if (ec)
68 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010069 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
70 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020071 return;
72 }
73 if (peerSocket)
74 {
75 // Something is wrong - socket shouldn't be acquired at this
76 // point
77 BMCWEB_LOG_ERROR
78 << "Failed to open connection - socket already used";
79 return;
80 }
81
82 BMCWEB_LOG_DEBUG << "Connection opened";
83 peerSocket = std::move(socket);
84 doRead();
85
86 // Trigger Write if any data was sent from server
87 // Initially this is negotiation chunk
88 doWrite();
89 });
90
91 auto mountHandler = [](const boost::system::error_code ec,
92 const bool status) {
93 if (ec)
94 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010095 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
96 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020097 return;
98 }
99 };
100
101 crow::connections::systemBus->async_method_call(
102 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
103 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
104 }
105
106 void send(const std::string_view data)
107 {
108 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
109 boost::asio::buffer(data));
110 ws2uxBuf.commit(data.size());
111 doWrite();
112 }
113
114 void close()
115 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100116 acceptor.close();
117 if (peerSocket)
118 {
119 BMCWEB_LOG_DEBUG << "peerSocket->close()";
120 peerSocket->close();
121 peerSocket.reset();
122 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
123 std::remove(socketId.c_str());
124 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200125 // The reference to session should exists until unmount is
126 // called
127 auto unmountHandler = [](const boost::system::error_code ec) {
128 if (ec)
129 {
130 BMCWEB_LOG_ERROR << "DBus error: " << ec
131 << ", cannot call unmount method";
132 return;
133 }
134 };
135
136 crow::connections::systemBus->async_method_call(
137 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
138 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
139 }
140
141 private:
142 void doRead()
143 {
144 if (!peerSocket)
145 {
146 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
147 // Skip if UNIX socket is not created yet.
148 return;
149 }
150
151 // Trigger async read
152 peerSocket->async_read_some(
153 ux2wsBuf.prepare(nbdBufferSize),
154 [this, self(shared_from_this())](boost::system::error_code ec,
155 std::size_t bytesRead) {
156 if (ec)
157 {
158 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
Iwona Winiarska123e8232019-11-29 12:34:33 +0100159 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200160 // UNIX socket has been closed by peer, best we can do is to
161 // break all connections
162 close();
163 return;
164 }
165
166 // Fetch data from UNIX socket
167
168 ux2wsBuf.commit(bytesRead);
169
170 // Paste it to WebSocket as binary
171 connection.sendBinary(
172 boost::beast::buffers_to_string(ux2wsBuf.data()));
173 ux2wsBuf.consume(bytesRead);
174
175 // Allow further reads
176 doRead();
177 });
178 }
179
180 void doWrite()
181 {
182 if (!peerSocket)
183 {
184 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
185 // Skip if UNIX socket is not created yet. Collect data, and wait
186 // for nbd-client connection
187 return;
188 }
189
190 if (uxWriteInProgress)
191 {
192 BMCWEB_LOG_ERROR << "Write in progress";
193 return;
194 }
195
196 if (ws2uxBuf.size() == 0)
197 {
198 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
199 return;
200 }
201
202 uxWriteInProgress = true;
203 boost::asio::async_write(
204 *peerSocket, ws2uxBuf.data(),
205 [this, self(shared_from_this())](boost::system::error_code ec,
206 std::size_t bytesWritten) {
207 ws2uxBuf.consume(bytesWritten);
208 uxWriteInProgress = false;
209 if (ec)
210 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100211 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
212 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200213 return;
214 }
215 // Retrigger doWrite if there is something in buffer
Iwona Winiarska123e8232019-11-29 12:34:33 +0100216 if (ws2uxBuf.size() > 0)
217 {
218 doWrite();
219 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200220 });
221 }
222
223 // Keeps UNIX socket endpoint file path
224 const std::string socketId;
225 const std::string endpointId;
226 const std::string path;
227
228 bool uxWriteInProgress = false;
229
230 // UNIX => WebSocket buffer
231 boost::beast::multi_buffer ux2wsBuf;
232
233 // WebSocket <= UNIX buffer
234 boost::beast::multi_buffer ws2uxBuf;
235
236 // Default acceptor for UNIX socket
237 stream_protocol::acceptor acceptor;
238
239 // The socket used to communicate with the client.
240 std::optional<stream_protocol::socket> peerSocket;
241
242 crow::websocket::Connection& connection;
243};
244
245static boost::container::flat_map<crow::websocket::Connection*,
246 std::shared_ptr<NbdProxyServer>>
247 sessions;
248
249void requestRoutes(CrowApp& app)
250{
251 BMCWEB_ROUTE(app, "/nbd/<str>")
252 .websocket()
253 .onopen([&app](crow::websocket::Connection& conn,
254 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
255 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
256
257 for (const auto session : sessions)
258 {
259 if (session.second->getEndpointId() == conn.req.target())
260 {
261 BMCWEB_LOG_ERROR
262 << "Cannot open new connection - socket is in use";
263 return;
264 }
265 }
266
267 auto openHandler = [asyncResp, &conn](
268 const boost::system::error_code ec,
269 dbus::utility::ManagedObjectType& objects) {
270 const std::string* socketValue = nullptr;
271 const std::string* endpointValue = nullptr;
272 const std::string* endpointObjectPath = nullptr;
273
274 if (ec)
275 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100276 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200277 return;
278 }
279
280 for (const auto& objectPath : objects)
281 {
282 const auto interfaceMap = objectPath.second.find(
283 "xyz.openbmc_project.VirtualMedia.MountPoint");
284
285 if (interfaceMap == objectPath.second.end())
286 {
287 BMCWEB_LOG_DEBUG << "Cannot find MountPoint object";
288 continue;
289 }
290
291 const auto endpoint =
292 interfaceMap->second.find("EndpointId");
293 if (endpoint == interfaceMap->second.end())
294 {
295 BMCWEB_LOG_DEBUG << "Cannot find EndpointId property";
296 continue;
297 }
298
299 endpointValue = std::get_if<std::string>(&endpoint->second);
300
301 if (endpointValue == nullptr)
302 {
303 BMCWEB_LOG_ERROR << "EndpointId property value is null";
304 continue;
305 }
306
307 if (*endpointValue == conn.req.target())
308 {
309 const auto socket = interfaceMap->second.find("Socket");
310 if (socket == interfaceMap->second.end())
311 {
312 BMCWEB_LOG_DEBUG << "Cannot find Socket property";
313 continue;
314 }
315
316 socketValue = std::get_if<std::string>(&socket->second);
317 if (socketValue == nullptr)
318 {
319 BMCWEB_LOG_ERROR << "Socket property value is null";
320 continue;
321 }
322
323 endpointObjectPath = &objectPath.first.str;
324 break;
325 }
326 }
327
328 if (endpointObjectPath == nullptr)
329 {
330 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
331 asyncResp->res.result(
332 boost::beast::http::status::not_found);
333 return;
334 }
335
336 // If the socket file exists (i.e. after bmcweb crash), we
337 // cannot reuse it.
338 std::remove((*socketValue).c_str());
339
340 sessions[&conn] = std::make_shared<NbdProxyServer>(
341 conn, std::move(*socketValue), std::move(*endpointValue),
342 std::move(*endpointObjectPath));
343
344 sessions[&conn]->run();
345
346 asyncResp->res.result(boost::beast::http::status::ok);
347 };
348 crow::connections::systemBus->async_method_call(
349 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
350 "/xyz/openbmc_project/VirtualMedia",
351 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
352 })
353 .onclose(
354 [](crow::websocket::Connection& conn, const std::string& reason) {
355 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
356 << "')";
357 auto session = sessions.find(&conn);
358 if (session == sessions.end())
359 {
360 BMCWEB_LOG_DEBUG << "No session to close";
361 return;
362 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200363 session->second->close();
Iwona Winiarska123e8232019-11-29 12:34:33 +0100364 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200365 sessions.erase(session);
366 })
367 .onmessage([](crow::websocket::Connection& conn,
368 const std::string& data, bool isBinary) {
369 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
370 << ")";
371 // Acquire proxy from sessions
372 auto session = sessions.find(&conn);
373 if (session != sessions.end())
374 {
375 if (session->second)
376 {
377 session->second->send(data);
378 return;
379 }
380 }
381 conn.close();
382 });
383}
384} // namespace nbd_proxy
385} // namespace crow