blob: 9607ddfb10d67c0f25ca7c2583644a0a52f480ba [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 Tanous4c521c32024-04-07 13:47:06 -070066 BMCWEB_LOG_DEBUG("std::filesystem::remove({})", socketId);
67 std::error_code ec2;
68 std::filesystem::remove(socketId.c_str(), ec2);
69 if (ec2)
70 {
71 BMCWEB_LOG_DEBUG("Failed to remove file, ignoring");
72 }
Ed Tanous863c1c22022-02-21 21:33:06 -080073
74 crow::connections::systemBus->async_method_call(
75 dbus::utility::logError, "xyz.openbmc_project.VirtualMedia", path,
76 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020077 }
78
79 std::string getEndpointId() const
80 {
81 return endpointId;
82 }
83
84 void run()
85 {
Ed Tanous863c1c22022-02-21 21:33:06 -080086 acceptor.async_accept(
87 [weak(weak_from_this())](const boost::system::error_code& ec,
88 stream_protocol::socket socket) {
Ed Tanous002d39b2022-05-31 08:59:27 -070089 if (ec)
90 {
Ed Tanous62598e32023-07-17 17:06:25 -070091 BMCWEB_LOG_ERROR("UNIX socket: async_accept error = {}",
92 ec.message());
Ed Tanous002d39b2022-05-31 08:59:27 -070093 return;
94 }
Ed Tanous863c1c22022-02-21 21:33:06 -080095
Ed Tanous62598e32023-07-17 17:06:25 -070096 BMCWEB_LOG_DEBUG("Connection opened");
Ed Tanous863c1c22022-02-21 21:33:06 -080097 std::shared_ptr<NbdProxyServer> self = weak.lock();
98 if (self == nullptr)
Ed Tanous002d39b2022-05-31 08:59:27 -070099 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700100 return;
101 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200102
Ed Tanous863c1c22022-02-21 21:33:06 -0800103 self->connection.resumeRead();
104 self->peerSocket = std::move(socket);
105 // Start reading from socket
106 self->doRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700107 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200108
Ed Tanous863c1c22022-02-21 21:33:06 -0800109 auto mountHandler = [weak(weak_from_this())](
110 const boost::system::error_code& ec, bool) {
111 std::shared_ptr<NbdProxyServer> self = weak.lock();
112 if (self == nullptr)
113 {
114 return;
115 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200116 if (ec)
117 {
Ed Tanous62598e32023-07-17 17:06:25 -0700118 BMCWEB_LOG_ERROR("DBus error: cannot call mount method = {}",
119 ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800120
121 self->connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200122 return;
123 }
124 };
125
126 crow::connections::systemBus->async_method_call(
127 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
128 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
129 }
130
Ed Tanous863c1c22022-02-21 21:33:06 -0800131 void send(std::string_view buffer, std::function<void()>&& onDone)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200132 {
Ed Tanous44106f32024-04-06 13:48:50 -0700133 size_t copied = boost::asio::buffer_copy(
134 ws2uxBuf.prepare(buffer.size()), boost::asio::buffer(buffer));
135 ws2uxBuf.commit(copied);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200136
Ed Tanous863c1c22022-02-21 21:33:06 -0800137 doWrite(std::move(onDone));
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200138 }
139
140 private:
141 void doRead()
142 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200143 // Trigger async read
Ed Tanous863c1c22022-02-21 21:33:06 -0800144 peerSocket.async_read_some(
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200145 ux2wsBuf.prepare(nbdBufferSize),
Ed Tanous863c1c22022-02-21 21:33:06 -0800146 [weak(weak_from_this())](const boost::system::error_code& ec,
147 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700148 if (ec)
149 {
Ed Tanous62598e32023-07-17 17:06:25 -0700150 BMCWEB_LOG_ERROR("UNIX socket: async_read_some error = {}",
151 ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800152 return;
153 }
154 std::shared_ptr<NbdProxyServer> self = weak.lock();
155 if (self == nullptr)
156 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700157 return;
158 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200159
Ed Tanous863c1c22022-02-21 21:33:06 -0800160 // Send to websocket
161 self->ux2wsBuf.commit(bytesRead);
162 self->connection.sendEx(
163 crow::websocket::MessageType::Binary,
164 boost::beast::buffers_to_string(self->ux2wsBuf.data()),
165 [weak(self->weak_from_this())]() {
166 std::shared_ptr<NbdProxyServer> self2 = weak.lock();
167 if (self2 != nullptr)
168 {
169 self2->ux2wsBuf.consume(self2->ux2wsBuf.size());
170 self2->doRead();
171 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200172 });
Patrick Williams5a39f772023-10-20 11:20:21 -0500173 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200174 }
175
Ed Tanous863c1c22022-02-21 21:33:06 -0800176 void doWrite(std::function<void()>&& onDone)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200177 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200178 if (uxWriteInProgress)
179 {
Ed Tanous62598e32023-07-17 17:06:25 -0700180 BMCWEB_LOG_ERROR("Write in progress");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200181 return;
182 }
183
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800184 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200185 {
Ed Tanous62598e32023-07-17 17:06:25 -0700186 BMCWEB_LOG_ERROR("No data to write to UNIX socket");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200187 return;
188 }
189
190 uxWriteInProgress = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800191 peerSocket.async_write_some(
192 ws2uxBuf.data(),
193 [weak(weak_from_this()),
194 onDone(std::move(onDone))](const boost::system::error_code& ec,
195 size_t bytesWritten) mutable {
196 std::shared_ptr<NbdProxyServer> self = weak.lock();
197 if (self == nullptr)
198 {
199 return;
200 }
201
202 self->ws2uxBuf.consume(bytesWritten);
203 self->uxWriteInProgress = false;
204
Ed Tanous002d39b2022-05-31 08:59:27 -0700205 if (ec)
206 {
Ed Tanous62598e32023-07-17 17:06:25 -0700207 BMCWEB_LOG_ERROR("UNIX: async_write error = {}", ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800208 self->connection.close("Internal error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700209 return;
210 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800211
Ed Tanous002d39b2022-05-31 08:59:27 -0700212 // Retrigger doWrite if there is something in buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800213 if (self->ws2uxBuf.size() > 0)
Ed Tanous002d39b2022-05-31 08:59:27 -0700214 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800215 self->doWrite(std::move(onDone));
216 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700217 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800218 onDone();
Patrick Williams5a39f772023-10-20 11:20:21 -0500219 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200220 }
221
222 // Keeps UNIX socket endpoint file path
223 const std::string socketId;
224 const std::string endpointId;
225 const std::string path;
226
227 bool uxWriteInProgress = false;
228
229 // UNIX => WebSocket buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800230 boost::beast::flat_static_buffer<nbdBufferSize> ux2wsBuf;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200231
Ed Tanous863c1c22022-02-21 21:33:06 -0800232 // WebSocket => UNIX buffer
233 boost::beast::flat_static_buffer<nbdBufferSize> ws2uxBuf;
234
235 // The socket used to communicate with the client.
236 stream_protocol::socket peerSocket;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200237
238 // Default acceptor for UNIX socket
239 stream_protocol::acceptor acceptor;
240
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200241 crow::websocket::Connection& connection;
242};
243
Ed Tanouscf9e4172022-12-21 09:30:16 -0800244using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
245 std::shared_ptr<NbdProxyServer>>;
246// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
247static SessionMap sessions;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200248
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800249inline void
250 afterGetManagedObjects(crow::websocket::Connection& conn,
251 const boost::system::error_code& ec,
252 const dbus::utility::ManagedObjectType& objects)
253{
254 const std::string* socketValue = nullptr;
255 const std::string* endpointValue = nullptr;
256 const std::string* endpointObjectPath = nullptr;
257
258 if (ec)
259 {
Ed Tanous62598e32023-07-17 17:06:25 -0700260 BMCWEB_LOG_ERROR("DBus error: {}", ec.message());
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800261 conn.close("Failed to create mount point");
262 return;
263 }
264
265 for (const auto& [objectPath, interfaces] : objects)
266 {
267 for (const auto& [interface, properties] : interfaces)
268 {
269 if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint")
270 {
271 continue;
272 }
273
274 for (const auto& [name, value] : properties)
275 {
276 if (name == "EndpointId")
277 {
278 endpointValue = std::get_if<std::string>(&value);
279
280 if (endpointValue == nullptr)
281 {
Ed Tanous62598e32023-07-17 17:06:25 -0700282 BMCWEB_LOG_ERROR("EndpointId property value is null");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800283 }
284 }
285 if (name == "Socket")
286 {
287 socketValue = std::get_if<std::string>(&value);
288 if (socketValue == nullptr)
289 {
Ed Tanous62598e32023-07-17 17:06:25 -0700290 BMCWEB_LOG_ERROR("Socket property value is null");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800291 }
292 }
293 }
294 }
295
296 if ((endpointValue != nullptr) && (socketValue != nullptr) &&
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800297 *endpointValue == conn.url().path())
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800298 {
299 endpointObjectPath = &objectPath.str;
300 break;
301 }
302 }
303
304 if (objects.empty() || endpointObjectPath == nullptr)
305 {
Ed Tanous62598e32023-07-17 17:06:25 -0700306 BMCWEB_LOG_ERROR("Cannot find requested EndpointId");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800307 conn.close("Failed to match EndpointId");
308 return;
309 }
310
311 for (const auto& session : sessions)
312 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800313 if (session.second->getEndpointId() == conn.url().path())
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800314 {
Ed Tanous62598e32023-07-17 17:06:25 -0700315 BMCWEB_LOG_ERROR("Cannot open new connection - socket is in use");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800316 conn.close("Slot is in use");
317 return;
318 }
319 }
320
321 // If the socket file exists (i.e. after bmcweb crash),
322 // we cannot reuse it.
323 std::remove((*socketValue).c_str());
324
325 sessions[&conn] = std::make_shared<NbdProxyServer>(
326 conn, *socketValue, *endpointValue, *endpointObjectPath);
327
328 sessions[&conn]->run();
329};
Ed Tanous8108fd32023-02-27 13:58:03 -0800330inline void onOpen(crow::websocket::Connection& conn)
331{
Ed Tanous62598e32023-07-17 17:06:25 -0700332 BMCWEB_LOG_DEBUG("nbd-proxy.onopen({})", logPtr(&conn));
Ed Tanous8108fd32023-02-27 13:58:03 -0800333
George Liu5eb468d2023-06-20 17:03:24 +0800334 sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia");
335 dbus::utility::getManagedObjects(
336 "xyz.openbmc_project.VirtualMedia", path,
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800337 [&conn](const boost::system::error_code& ec,
Ed Tanouse551b5f2023-02-27 14:19:07 -0800338 const dbus::utility::ManagedObjectType& objects) {
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800339 afterGetManagedObjects(conn, ec, objects);
Patrick Williams5a39f772023-10-20 11:20:21 -0500340 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800341
342 // We need to wait for dbus and the websockets to hook up before data is
343 // sent/received. Tell the core to hold off messages until the sockets are
344 // up
345 conn.deferRead();
Ed Tanous8108fd32023-02-27 13:58:03 -0800346}
347
348inline void onClose(crow::websocket::Connection& conn,
349 const std::string& reason)
350{
Ed Tanous62598e32023-07-17 17:06:25 -0700351 BMCWEB_LOG_DEBUG("nbd-proxy.onclose(reason = '{}')", reason);
Ed Tanous8108fd32023-02-27 13:58:03 -0800352 auto session = sessions.find(&conn);
353 if (session == sessions.end())
354 {
Ed Tanous62598e32023-07-17 17:06:25 -0700355 BMCWEB_LOG_DEBUG("No session to close");
Ed Tanous8108fd32023-02-27 13:58:03 -0800356 return;
357 }
Ed Tanous8108fd32023-02-27 13:58:03 -0800358 // Remove reference to session in global map
359 sessions.erase(session);
360}
361
Ed Tanous863c1c22022-02-21 21:33:06 -0800362inline void onMessage(crow::websocket::Connection& conn, std::string_view data,
363 crow::websocket::MessageType /*type*/,
364 std::function<void()>&& whenComplete)
Ed Tanous8108fd32023-02-27 13:58:03 -0800365{
Ed Tanous62598e32023-07-17 17:06:25 -0700366 BMCWEB_LOG_DEBUG("nbd-proxy.onMessage(len = {})", data.size());
Ed Tanous863c1c22022-02-21 21:33:06 -0800367
Ed Tanous8108fd32023-02-27 13:58:03 -0800368 // Acquire proxy from sessions
369 auto session = sessions.find(&conn);
Ed Tanous863c1c22022-02-21 21:33:06 -0800370 if (session == sessions.end() || session->second == nullptr)
Ed Tanous8108fd32023-02-27 13:58:03 -0800371 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800372 whenComplete();
373 return;
Ed Tanous8108fd32023-02-27 13:58:03 -0800374 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800375
376 session->second->send(data, std::move(whenComplete));
Ed Tanous8108fd32023-02-27 13:58:03 -0800377}
378
Ed Tanous81ce6092020-12-17 16:54:55 +0000379inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200380{
381 BMCWEB_ROUTE(app, "/nbd/<str>")
382 .websocket()
Ed Tanous8108fd32023-02-27 13:58:03 -0800383 .onopen(onOpen)
384 .onclose(onClose)
Ed Tanous863c1c22022-02-21 21:33:06 -0800385 .onmessageex(onMessage);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200386}
387} // namespace nbd_proxy
388} // namespace crow