blob: 62bd32436339f311b72255a814a06904bff4aaf6 [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
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080017#include "app.hpp"
18#include "dbus_utility.hpp"
19#include "privileges.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -070020#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080021
Ed Tanousd43cd0c2020-09-30 20:46:53 -070022#include <boost/asio/local/stream_protocol.hpp>
23#include <boost/asio/write.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020024#include <boost/beast/core/buffers_to_string.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020025#include <boost/container/flat_map.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020026
Ed Tanous863c1c22022-02-21 21:33:06 -080027#include <string_view>
28
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020029namespace crow
30{
31
32namespace nbd_proxy
33{
34
35using boost::asio::local::stream_protocol;
36
Ed Tanous863c1c22022-02-21 21:33:06 -080037static constexpr size_t nbdBufferSize = 131088;
Ed Tanouscf9e4172022-12-21 09:30:16 -080038constexpr const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020039
40struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
41{
42 NbdProxyServer(crow::websocket::Connection& connIn,
43 const std::string& socketIdIn,
44 const std::string& endpointIdIn, const std::string& pathIn) :
45 socketId(socketIdIn),
46 endpointId(endpointIdIn), path(pathIn),
Ed Tanous863c1c22022-02-21 21:33:06 -080047
48 peerSocket(connIn.getIoContext()),
Ed Tanous2c70f802020-09-28 14:29:23 -070049 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020050 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050051 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020052
Ed Tanous08e9fa92022-02-16 16:18:08 -080053 NbdProxyServer(const NbdProxyServer&) = delete;
54 NbdProxyServer(NbdProxyServer&&) = delete;
55 NbdProxyServer& operator=(const NbdProxyServer&) = delete;
56 NbdProxyServer& operator=(NbdProxyServer&&) = delete;
57
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020058 ~NbdProxyServer()
59 {
60 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Ed Tanous863c1c22022-02-21 21:33:06 -080061
62 BMCWEB_LOG_DEBUG << "peerSocket->close()";
63 boost::system::error_code ec;
64 peerSocket.close(ec);
65
66 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
67 std::remove(socketId.c_str());
68
69 crow::connections::systemBus->async_method_call(
70 dbus::utility::logError, "xyz.openbmc_project.VirtualMedia", path,
71 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020072 }
73
74 std::string getEndpointId() const
75 {
76 return endpointId;
77 }
78
79 void run()
80 {
Ed Tanous863c1c22022-02-21 21:33:06 -080081 acceptor.async_accept(
82 [weak(weak_from_this())](const boost::system::error_code& ec,
83 stream_protocol::socket socket) {
Ed Tanous002d39b2022-05-31 08:59:27 -070084 if (ec)
85 {
86 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
87 << ec.message();
88 return;
89 }
Ed Tanous863c1c22022-02-21 21:33:06 -080090
91 BMCWEB_LOG_DEBUG << "Connection opened";
92 std::shared_ptr<NbdProxyServer> self = weak.lock();
93 if (self == nullptr)
Ed Tanous002d39b2022-05-31 08:59:27 -070094 {
Ed Tanous002d39b2022-05-31 08:59:27 -070095 return;
96 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020097
Ed Tanous863c1c22022-02-21 21:33:06 -080098 self->connection.resumeRead();
99 self->peerSocket = std::move(socket);
100 // Start reading from socket
101 self->doRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700102 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200103
Ed Tanous863c1c22022-02-21 21:33:06 -0800104 auto mountHandler = [weak(weak_from_this())](
105 const boost::system::error_code& ec, bool) {
106 std::shared_ptr<NbdProxyServer> self = weak.lock();
107 if (self == nullptr)
108 {
109 return;
110 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200111 if (ec)
112 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100113 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
114 << ec.message();
Ed Tanous863c1c22022-02-21 21:33:06 -0800115
116 self->connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200117 return;
118 }
119 };
120
121 crow::connections::systemBus->async_method_call(
122 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
123 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
124 }
125
Ed Tanous863c1c22022-02-21 21:33:06 -0800126 void send(std::string_view buffer, std::function<void()>&& onDone)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200127 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800128 boost::asio::buffer_copy(ws2uxBuf.prepare(buffer.size()),
129 boost::asio::buffer(buffer));
130 ws2uxBuf.commit(buffer.size());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200131
Ed Tanous863c1c22022-02-21 21:33:06 -0800132 doWrite(std::move(onDone));
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200133 }
134
135 private:
136 void doRead()
137 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200138 // Trigger async read
Ed Tanous863c1c22022-02-21 21:33:06 -0800139 peerSocket.async_read_some(
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200140 ux2wsBuf.prepare(nbdBufferSize),
Ed Tanous863c1c22022-02-21 21:33:06 -0800141 [weak(weak_from_this())](const boost::system::error_code& ec,
142 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700143 if (ec)
144 {
145 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
146 << ec.message();
Ed Tanous863c1c22022-02-21 21:33:06 -0800147 return;
148 }
149 std::shared_ptr<NbdProxyServer> self = weak.lock();
150 if (self == nullptr)
151 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700152 return;
153 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200154
Ed Tanous863c1c22022-02-21 21:33:06 -0800155 // Send to websocket
156 self->ux2wsBuf.commit(bytesRead);
157 self->connection.sendEx(
158 crow::websocket::MessageType::Binary,
159 boost::beast::buffers_to_string(self->ux2wsBuf.data()),
160 [weak(self->weak_from_this())]() {
161 std::shared_ptr<NbdProxyServer> self2 = weak.lock();
162 if (self2 != nullptr)
163 {
164 self2->ux2wsBuf.consume(self2->ux2wsBuf.size());
165 self2->doRead();
166 }
167 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200168 });
169 }
170
Ed Tanous863c1c22022-02-21 21:33:06 -0800171 void doWrite(std::function<void()>&& onDone)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200172 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200173 if (uxWriteInProgress)
174 {
175 BMCWEB_LOG_ERROR << "Write in progress";
176 return;
177 }
178
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800179 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200180 {
181 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
182 return;
183 }
184
185 uxWriteInProgress = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800186 peerSocket.async_write_some(
187 ws2uxBuf.data(),
188 [weak(weak_from_this()),
189 onDone(std::move(onDone))](const boost::system::error_code& ec,
190 size_t bytesWritten) mutable {
191 std::shared_ptr<NbdProxyServer> self = weak.lock();
192 if (self == nullptr)
193 {
194 return;
195 }
196
197 self->ws2uxBuf.consume(bytesWritten);
198 self->uxWriteInProgress = false;
199
Ed Tanous002d39b2022-05-31 08:59:27 -0700200 if (ec)
201 {
202 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
203 << ec.message();
Ed Tanous863c1c22022-02-21 21:33:06 -0800204 self->connection.close("Internal error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700205 return;
206 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800207
Ed Tanous002d39b2022-05-31 08:59:27 -0700208 // Retrigger doWrite if there is something in buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800209 if (self->ws2uxBuf.size() > 0)
Ed Tanous002d39b2022-05-31 08:59:27 -0700210 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800211 self->doWrite(std::move(onDone));
212 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700213 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800214 onDone();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200215 });
216 }
217
218 // Keeps UNIX socket endpoint file path
219 const std::string socketId;
220 const std::string endpointId;
221 const std::string path;
222
223 bool uxWriteInProgress = false;
224
225 // UNIX => WebSocket buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800226 boost::beast::flat_static_buffer<nbdBufferSize> ux2wsBuf;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200227
Ed Tanous863c1c22022-02-21 21:33:06 -0800228 // WebSocket => UNIX buffer
229 boost::beast::flat_static_buffer<nbdBufferSize> ws2uxBuf;
230
231 // The socket used to communicate with the client.
232 stream_protocol::socket peerSocket;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200233
234 // Default acceptor for UNIX socket
235 stream_protocol::acceptor acceptor;
236
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200237 crow::websocket::Connection& connection;
238};
239
Ed Tanouscf9e4172022-12-21 09:30:16 -0800240using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
241 std::shared_ptr<NbdProxyServer>>;
242// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
243static SessionMap sessions;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200244
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800245inline void
246 afterGetManagedObjects(crow::websocket::Connection& conn,
247 const boost::system::error_code& ec,
248 const dbus::utility::ManagedObjectType& objects)
249{
250 const std::string* socketValue = nullptr;
251 const std::string* endpointValue = nullptr;
252 const std::string* endpointObjectPath = nullptr;
253
254 if (ec)
255 {
256 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
257 conn.close("Failed to create mount point");
258 return;
259 }
260
261 for (const auto& [objectPath, interfaces] : objects)
262 {
263 for (const auto& [interface, properties] : interfaces)
264 {
265 if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint")
266 {
267 continue;
268 }
269
270 for (const auto& [name, value] : properties)
271 {
272 if (name == "EndpointId")
273 {
274 endpointValue = std::get_if<std::string>(&value);
275
276 if (endpointValue == nullptr)
277 {
278 BMCWEB_LOG_ERROR << "EndpointId property value is null";
279 }
280 }
281 if (name == "Socket")
282 {
283 socketValue = std::get_if<std::string>(&value);
284 if (socketValue == nullptr)
285 {
286 BMCWEB_LOG_ERROR << "Socket property value is null";
287 }
288 }
289 }
290 }
291
292 if ((endpointValue != nullptr) && (socketValue != nullptr) &&
293 *endpointValue == conn.req.target())
294 {
295 endpointObjectPath = &objectPath.str;
296 break;
297 }
298 }
299
300 if (objects.empty() || endpointObjectPath == nullptr)
301 {
302 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
303 conn.close("Failed to match EndpointId");
304 return;
305 }
306
307 for (const auto& session : sessions)
308 {
309 if (session.second->getEndpointId() == conn.req.target())
310 {
311 BMCWEB_LOG_ERROR << "Cannot open new connection - socket is in use";
312 conn.close("Slot is in use");
313 return;
314 }
315 }
316
317 // If the socket file exists (i.e. after bmcweb crash),
318 // we cannot reuse it.
319 std::remove((*socketValue).c_str());
320
321 sessions[&conn] = std::make_shared<NbdProxyServer>(
322 conn, *socketValue, *endpointValue, *endpointObjectPath);
323
324 sessions[&conn]->run();
325};
Ed Tanous8108fd32023-02-27 13:58:03 -0800326inline void onOpen(crow::websocket::Connection& conn)
327{
328 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
329
George Liu5eb468d2023-06-20 17:03:24 +0800330 sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia");
331 dbus::utility::getManagedObjects(
332 "xyz.openbmc_project.VirtualMedia", path,
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800333 [&conn](const boost::system::error_code& ec,
Ed Tanouse551b5f2023-02-27 14:19:07 -0800334 const dbus::utility::ManagedObjectType& objects) {
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800335 afterGetManagedObjects(conn, ec, objects);
George Liu5eb468d2023-06-20 17:03:24 +0800336 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800337
338 // We need to wait for dbus and the websockets to hook up before data is
339 // sent/received. Tell the core to hold off messages until the sockets are
340 // up
341 conn.deferRead();
Ed Tanous8108fd32023-02-27 13:58:03 -0800342}
343
344inline void onClose(crow::websocket::Connection& conn,
345 const std::string& reason)
346{
347 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason << "')";
348 auto session = sessions.find(&conn);
349 if (session == sessions.end())
350 {
351 BMCWEB_LOG_DEBUG << "No session to close";
352 return;
353 }
Ed Tanous8108fd32023-02-27 13:58:03 -0800354 // Remove reference to session in global map
355 sessions.erase(session);
356}
357
Ed Tanous863c1c22022-02-21 21:33:06 -0800358inline void onMessage(crow::websocket::Connection& conn, std::string_view data,
359 crow::websocket::MessageType /*type*/,
360 std::function<void()>&& whenComplete)
Ed Tanous8108fd32023-02-27 13:58:03 -0800361{
Ed Tanous863c1c22022-02-21 21:33:06 -0800362 BMCWEB_LOG_DEBUG << "nbd-proxy.onMessage(len = " << data.size() << ")";
363
Ed Tanous8108fd32023-02-27 13:58:03 -0800364 // Acquire proxy from sessions
365 auto session = sessions.find(&conn);
Ed Tanous863c1c22022-02-21 21:33:06 -0800366 if (session == sessions.end() || session->second == nullptr)
Ed Tanous8108fd32023-02-27 13:58:03 -0800367 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800368 whenComplete();
369 return;
Ed Tanous8108fd32023-02-27 13:58:03 -0800370 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800371
372 session->second->send(data, std::move(whenComplete));
Ed Tanous8108fd32023-02-27 13:58:03 -0800373}
374
Ed Tanous81ce6092020-12-17 16:54:55 +0000375inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200376{
377 BMCWEB_ROUTE(app, "/nbd/<str>")
378 .websocket()
Ed Tanous8108fd32023-02-27 13:58:03 -0800379 .onopen(onOpen)
380 .onclose(onClose)
Ed Tanous863c1c22022-02-21 21:33:06 -0800381 .onmessageex(onMessage);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200382}
383} // namespace nbd_proxy
384} // namespace crow