blob: 5de3039765e457e61fdb455862062bfe696ef07a [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 {
Ed Tanous62598e32023-07-17 17:06:25 -070060 BMCWEB_LOG_DEBUG("NbdProxyServer destructor");
Ed Tanous863c1c22022-02-21 21:33:06 -080061
Ed Tanous62598e32023-07-17 17:06:25 -070062 BMCWEB_LOG_DEBUG("peerSocket->close()");
Ed Tanous863c1c22022-02-21 21:33:06 -080063 boost::system::error_code ec;
64 peerSocket.close(ec);
65
Ed Tanous62598e32023-07-17 17:06:25 -070066 BMCWEB_LOG_DEBUG("std::remove({})", socketId);
Ed Tanous863c1c22022-02-21 21:33:06 -080067 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 {
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_ERROR("UNIX socket: async_accept error = {}",
87 ec.message());
Ed Tanous002d39b2022-05-31 08:59:27 -070088 return;
89 }
Ed Tanous863c1c22022-02-21 21:33:06 -080090
Ed Tanous62598e32023-07-17 17:06:25 -070091 BMCWEB_LOG_DEBUG("Connection opened");
Ed Tanous863c1c22022-02-21 21:33:06 -080092 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700113 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700145 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700175 BMCWEB_LOG_ERROR("Write in progress");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200176 return;
177 }
178
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800179 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200180 {
Ed Tanous62598e32023-07-17 17:06:25 -0700181 BMCWEB_LOG_ERROR("No data to write to UNIX socket");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200182 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700202 BMCWEB_LOG_ERROR("UNIX: async_write error = {}", ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800203 self->connection.close("Internal error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700204 return;
205 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800206
Ed Tanous002d39b2022-05-31 08:59:27 -0700207 // Retrigger doWrite if there is something in buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800208 if (self->ws2uxBuf.size() > 0)
Ed Tanous002d39b2022-05-31 08:59:27 -0700209 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800210 self->doWrite(std::move(onDone));
211 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700212 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800213 onDone();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200214 });
215 }
216
217 // Keeps UNIX socket endpoint file path
218 const std::string socketId;
219 const std::string endpointId;
220 const std::string path;
221
222 bool uxWriteInProgress = false;
223
224 // UNIX => WebSocket buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800225 boost::beast::flat_static_buffer<nbdBufferSize> ux2wsBuf;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200226
Ed Tanous863c1c22022-02-21 21:33:06 -0800227 // WebSocket => UNIX buffer
228 boost::beast::flat_static_buffer<nbdBufferSize> ws2uxBuf;
229
230 // The socket used to communicate with the client.
231 stream_protocol::socket peerSocket;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200232
233 // Default acceptor for UNIX socket
234 stream_protocol::acceptor acceptor;
235
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200236 crow::websocket::Connection& connection;
237};
238
Ed Tanouscf9e4172022-12-21 09:30:16 -0800239using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
240 std::shared_ptr<NbdProxyServer>>;
241// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
242static SessionMap sessions;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200243
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800244inline void
245 afterGetManagedObjects(crow::websocket::Connection& conn,
246 const boost::system::error_code& ec,
247 const dbus::utility::ManagedObjectType& objects)
248{
249 const std::string* socketValue = nullptr;
250 const std::string* endpointValue = nullptr;
251 const std::string* endpointObjectPath = nullptr;
252
253 if (ec)
254 {
Ed Tanous62598e32023-07-17 17:06:25 -0700255 BMCWEB_LOG_ERROR("DBus error: {}", ec.message());
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800256 conn.close("Failed to create mount point");
257 return;
258 }
259
260 for (const auto& [objectPath, interfaces] : objects)
261 {
262 for (const auto& [interface, properties] : interfaces)
263 {
264 if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint")
265 {
266 continue;
267 }
268
269 for (const auto& [name, value] : properties)
270 {
271 if (name == "EndpointId")
272 {
273 endpointValue = std::get_if<std::string>(&value);
274
275 if (endpointValue == nullptr)
276 {
Ed Tanous62598e32023-07-17 17:06:25 -0700277 BMCWEB_LOG_ERROR("EndpointId property value is null");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800278 }
279 }
280 if (name == "Socket")
281 {
282 socketValue = std::get_if<std::string>(&value);
283 if (socketValue == nullptr)
284 {
Ed Tanous62598e32023-07-17 17:06:25 -0700285 BMCWEB_LOG_ERROR("Socket property value is null");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800286 }
287 }
288 }
289 }
290
291 if ((endpointValue != nullptr) && (socketValue != nullptr) &&
292 *endpointValue == conn.req.target())
293 {
294 endpointObjectPath = &objectPath.str;
295 break;
296 }
297 }
298
299 if (objects.empty() || endpointObjectPath == nullptr)
300 {
Ed Tanous62598e32023-07-17 17:06:25 -0700301 BMCWEB_LOG_ERROR("Cannot find requested EndpointId");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800302 conn.close("Failed to match EndpointId");
303 return;
304 }
305
306 for (const auto& session : sessions)
307 {
308 if (session.second->getEndpointId() == conn.req.target())
309 {
Ed Tanous62598e32023-07-17 17:06:25 -0700310 BMCWEB_LOG_ERROR("Cannot open new connection - socket is in use");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800311 conn.close("Slot is in use");
312 return;
313 }
314 }
315
316 // If the socket file exists (i.e. after bmcweb crash),
317 // we cannot reuse it.
318 std::remove((*socketValue).c_str());
319
320 sessions[&conn] = std::make_shared<NbdProxyServer>(
321 conn, *socketValue, *endpointValue, *endpointObjectPath);
322
323 sessions[&conn]->run();
324};
Ed Tanous8108fd32023-02-27 13:58:03 -0800325inline void onOpen(crow::websocket::Connection& conn)
326{
Ed Tanous62598e32023-07-17 17:06:25 -0700327 BMCWEB_LOG_DEBUG("nbd-proxy.onopen({})", logPtr(&conn));
Ed Tanous8108fd32023-02-27 13:58:03 -0800328
George Liu5eb468d2023-06-20 17:03:24 +0800329 sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia");
330 dbus::utility::getManagedObjects(
331 "xyz.openbmc_project.VirtualMedia", path,
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800332 [&conn](const boost::system::error_code& ec,
Ed Tanouse551b5f2023-02-27 14:19:07 -0800333 const dbus::utility::ManagedObjectType& objects) {
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800334 afterGetManagedObjects(conn, ec, objects);
George Liu5eb468d2023-06-20 17:03:24 +0800335 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800336
337 // We need to wait for dbus and the websockets to hook up before data is
338 // sent/received. Tell the core to hold off messages until the sockets are
339 // up
340 conn.deferRead();
Ed Tanous8108fd32023-02-27 13:58:03 -0800341}
342
343inline void onClose(crow::websocket::Connection& conn,
344 const std::string& reason)
345{
Ed Tanous62598e32023-07-17 17:06:25 -0700346 BMCWEB_LOG_DEBUG("nbd-proxy.onclose(reason = '{}')", reason);
Ed Tanous8108fd32023-02-27 13:58:03 -0800347 auto session = sessions.find(&conn);
348 if (session == sessions.end())
349 {
Ed Tanous62598e32023-07-17 17:06:25 -0700350 BMCWEB_LOG_DEBUG("No session to close");
Ed Tanous8108fd32023-02-27 13:58:03 -0800351 return;
352 }
Ed Tanous8108fd32023-02-27 13:58:03 -0800353 // Remove reference to session in global map
354 sessions.erase(session);
355}
356
Ed Tanous863c1c22022-02-21 21:33:06 -0800357inline void onMessage(crow::websocket::Connection& conn, std::string_view data,
358 crow::websocket::MessageType /*type*/,
359 std::function<void()>&& whenComplete)
Ed Tanous8108fd32023-02-27 13:58:03 -0800360{
Ed Tanous62598e32023-07-17 17:06:25 -0700361 BMCWEB_LOG_DEBUG("nbd-proxy.onMessage(len = {})", data.size());
Ed Tanous863c1c22022-02-21 21:33:06 -0800362
Ed Tanous8108fd32023-02-27 13:58:03 -0800363 // Acquire proxy from sessions
364 auto session = sessions.find(&conn);
Ed Tanous863c1c22022-02-21 21:33:06 -0800365 if (session == sessions.end() || session->second == nullptr)
Ed Tanous8108fd32023-02-27 13:58:03 -0800366 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800367 whenComplete();
368 return;
Ed Tanous8108fd32023-02-27 13:58:03 -0800369 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800370
371 session->second->send(data, std::move(whenComplete));
Ed Tanous8108fd32023-02-27 13:58:03 -0800372}
373
Ed Tanous81ce6092020-12-17 16:54:55 +0000374inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200375{
376 BMCWEB_ROUTE(app, "/nbd/<str>")
377 .websocket()
Ed Tanous8108fd32023-02-27 13:58:03 -0800378 .onopen(onOpen)
379 .onclose(onClose)
Ed Tanous863c1c22022-02-21 21:33:06 -0800380 .onmessageex(onMessage);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200381}
382} // namespace nbd_proxy
383} // namespace crow