blob: adc51a85ef283a62eeddd4da80ef83fd1d3bf0e2 [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>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020023#include <boost/beast/core/buffers_to_string.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020024#include <boost/container/flat_map.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020025
Ed Tanous863c1c22022-02-21 21:33:06 -080026#include <string_view>
27
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020028namespace crow
29{
30
31namespace nbd_proxy
32{
33
34using boost::asio::local::stream_protocol;
35
Ed Tanous863c1c22022-02-21 21:33:06 -080036static constexpr size_t nbdBufferSize = 131088;
Ed Tanouscf9e4172022-12-21 09:30:16 -080037constexpr const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020038
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),
Ed Tanous863c1c22022-02-21 21:33:06 -080046
47 peerSocket(connIn.getIoContext()),
Ed Tanous2c70f802020-09-28 14:29:23 -070048 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020049 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050050 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020051
Ed Tanous08e9fa92022-02-16 16:18:08 -080052 NbdProxyServer(const NbdProxyServer&) = delete;
53 NbdProxyServer(NbdProxyServer&&) = delete;
54 NbdProxyServer& operator=(const NbdProxyServer&) = delete;
55 NbdProxyServer& operator=(NbdProxyServer&&) = delete;
56
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020057 ~NbdProxyServer()
58 {
Ed Tanous62598e32023-07-17 17:06:25 -070059 BMCWEB_LOG_DEBUG("NbdProxyServer destructor");
Ed Tanous863c1c22022-02-21 21:33:06 -080060
Ed Tanous62598e32023-07-17 17:06:25 -070061 BMCWEB_LOG_DEBUG("peerSocket->close()");
Ed Tanous863c1c22022-02-21 21:33:06 -080062 boost::system::error_code ec;
63 peerSocket.close(ec);
64
Ed Tanous4c521c32024-04-07 13:47:06 -070065 BMCWEB_LOG_DEBUG("std::filesystem::remove({})", socketId);
66 std::error_code ec2;
67 std::filesystem::remove(socketId.c_str(), ec2);
68 if (ec2)
69 {
70 BMCWEB_LOG_DEBUG("Failed to remove file, ignoring");
71 }
Ed Tanous863c1c22022-02-21 21:33:06 -080072
73 crow::connections::systemBus->async_method_call(
74 dbus::utility::logError, "xyz.openbmc_project.VirtualMedia", path,
75 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020076 }
77
78 std::string getEndpointId() const
79 {
80 return endpointId;
81 }
82
83 void run()
84 {
Ed Tanous863c1c22022-02-21 21:33:06 -080085 acceptor.async_accept(
86 [weak(weak_from_this())](const boost::system::error_code& ec,
87 stream_protocol::socket socket) {
Ed Tanous002d39b2022-05-31 08:59:27 -070088 if (ec)
89 {
Ed Tanous62598e32023-07-17 17:06:25 -070090 BMCWEB_LOG_ERROR("UNIX socket: async_accept error = {}",
91 ec.message());
Ed Tanous002d39b2022-05-31 08:59:27 -070092 return;
93 }
Ed Tanous863c1c22022-02-21 21:33:06 -080094
Ed Tanous62598e32023-07-17 17:06:25 -070095 BMCWEB_LOG_DEBUG("Connection opened");
Ed Tanous863c1c22022-02-21 21:33:06 -080096 std::shared_ptr<NbdProxyServer> self = weak.lock();
97 if (self == nullptr)
Ed Tanous002d39b2022-05-31 08:59:27 -070098 {
Ed Tanous002d39b2022-05-31 08:59:27 -070099 return;
100 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200101
Ed Tanous863c1c22022-02-21 21:33:06 -0800102 self->connection.resumeRead();
103 self->peerSocket = std::move(socket);
104 // Start reading from socket
105 self->doRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700106 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200107
Ed Tanous863c1c22022-02-21 21:33:06 -0800108 auto mountHandler = [weak(weak_from_this())](
109 const boost::system::error_code& ec, bool) {
110 std::shared_ptr<NbdProxyServer> self = weak.lock();
111 if (self == nullptr)
112 {
113 return;
114 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200115 if (ec)
116 {
Ed Tanous62598e32023-07-17 17:06:25 -0700117 BMCWEB_LOG_ERROR("DBus error: cannot call mount method = {}",
118 ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800119
120 self->connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200121 return;
122 }
123 };
124
125 crow::connections::systemBus->async_method_call(
126 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
127 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
128 }
129
Ed Tanous863c1c22022-02-21 21:33:06 -0800130 void send(std::string_view buffer, std::function<void()>&& onDone)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200131 {
Ed Tanous44106f32024-04-06 13:48:50 -0700132 size_t copied = boost::asio::buffer_copy(
133 ws2uxBuf.prepare(buffer.size()), boost::asio::buffer(buffer));
134 ws2uxBuf.commit(copied);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200135
Ed Tanous863c1c22022-02-21 21:33:06 -0800136 doWrite(std::move(onDone));
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200137 }
138
139 private:
140 void doRead()
141 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200142 // Trigger async read
Ed Tanous863c1c22022-02-21 21:33:06 -0800143 peerSocket.async_read_some(
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200144 ux2wsBuf.prepare(nbdBufferSize),
Ed Tanous863c1c22022-02-21 21:33:06 -0800145 [weak(weak_from_this())](const boost::system::error_code& ec,
146 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700147 if (ec)
148 {
Ed Tanous62598e32023-07-17 17:06:25 -0700149 BMCWEB_LOG_ERROR("UNIX socket: async_read_some error = {}",
150 ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800151 return;
152 }
153 std::shared_ptr<NbdProxyServer> self = weak.lock();
154 if (self == nullptr)
155 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700156 return;
157 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200158
Ed Tanous863c1c22022-02-21 21:33:06 -0800159 // Send to websocket
160 self->ux2wsBuf.commit(bytesRead);
161 self->connection.sendEx(
162 crow::websocket::MessageType::Binary,
163 boost::beast::buffers_to_string(self->ux2wsBuf.data()),
164 [weak(self->weak_from_this())]() {
165 std::shared_ptr<NbdProxyServer> self2 = weak.lock();
166 if (self2 != nullptr)
167 {
168 self2->ux2wsBuf.consume(self2->ux2wsBuf.size());
169 self2->doRead();
170 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200171 });
Patrick Williams5a39f772023-10-20 11:20:21 -0500172 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200173 }
174
Ed Tanous863c1c22022-02-21 21:33:06 -0800175 void doWrite(std::function<void()>&& onDone)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200176 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200177 if (uxWriteInProgress)
178 {
Ed Tanous62598e32023-07-17 17:06:25 -0700179 BMCWEB_LOG_ERROR("Write in progress");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200180 return;
181 }
182
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800183 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200184 {
Ed Tanous62598e32023-07-17 17:06:25 -0700185 BMCWEB_LOG_ERROR("No data to write to UNIX socket");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200186 return;
187 }
188
189 uxWriteInProgress = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800190 peerSocket.async_write_some(
191 ws2uxBuf.data(),
192 [weak(weak_from_this()),
193 onDone(std::move(onDone))](const boost::system::error_code& ec,
194 size_t bytesWritten) mutable {
195 std::shared_ptr<NbdProxyServer> self = weak.lock();
196 if (self == nullptr)
197 {
198 return;
199 }
200
201 self->ws2uxBuf.consume(bytesWritten);
202 self->uxWriteInProgress = false;
203
Ed Tanous002d39b2022-05-31 08:59:27 -0700204 if (ec)
205 {
Ed Tanous62598e32023-07-17 17:06:25 -0700206 BMCWEB_LOG_ERROR("UNIX: async_write error = {}", ec.message());
Ed Tanous863c1c22022-02-21 21:33:06 -0800207 self->connection.close("Internal error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700208 return;
209 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800210
Ed Tanous002d39b2022-05-31 08:59:27 -0700211 // Retrigger doWrite if there is something in buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800212 if (self->ws2uxBuf.size() > 0)
Ed Tanous002d39b2022-05-31 08:59:27 -0700213 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800214 self->doWrite(std::move(onDone));
215 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700216 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800217 onDone();
Patrick Williams5a39f772023-10-20 11:20:21 -0500218 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200219 }
220
221 // Keeps UNIX socket endpoint file path
222 const std::string socketId;
223 const std::string endpointId;
224 const std::string path;
225
226 bool uxWriteInProgress = false;
227
228 // UNIX => WebSocket buffer
Ed Tanous863c1c22022-02-21 21:33:06 -0800229 boost::beast::flat_static_buffer<nbdBufferSize> ux2wsBuf;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200230
Ed Tanous863c1c22022-02-21 21:33:06 -0800231 // WebSocket => UNIX buffer
232 boost::beast::flat_static_buffer<nbdBufferSize> ws2uxBuf;
233
234 // The socket used to communicate with the client.
235 stream_protocol::socket peerSocket;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200236
237 // Default acceptor for UNIX socket
238 stream_protocol::acceptor acceptor;
239
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200240 crow::websocket::Connection& connection;
241};
242
Ed Tanouscf9e4172022-12-21 09:30:16 -0800243using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
244 std::shared_ptr<NbdProxyServer>>;
245// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
246static SessionMap sessions;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200247
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800248inline void
249 afterGetManagedObjects(crow::websocket::Connection& conn,
250 const boost::system::error_code& ec,
251 const dbus::utility::ManagedObjectType& objects)
252{
253 const std::string* socketValue = nullptr;
254 const std::string* endpointValue = nullptr;
255 const std::string* endpointObjectPath = nullptr;
256
257 if (ec)
258 {
Ed Tanous62598e32023-07-17 17:06:25 -0700259 BMCWEB_LOG_ERROR("DBus error: {}", ec.message());
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800260 conn.close("Failed to create mount point");
261 return;
262 }
263
264 for (const auto& [objectPath, interfaces] : objects)
265 {
266 for (const auto& [interface, properties] : interfaces)
267 {
268 if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint")
269 {
270 continue;
271 }
272
273 for (const auto& [name, value] : properties)
274 {
275 if (name == "EndpointId")
276 {
277 endpointValue = std::get_if<std::string>(&value);
278
279 if (endpointValue == nullptr)
280 {
Ed Tanous62598e32023-07-17 17:06:25 -0700281 BMCWEB_LOG_ERROR("EndpointId property value is null");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800282 }
283 }
284 if (name == "Socket")
285 {
286 socketValue = std::get_if<std::string>(&value);
287 if (socketValue == nullptr)
288 {
Ed Tanous62598e32023-07-17 17:06:25 -0700289 BMCWEB_LOG_ERROR("Socket property value is null");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800290 }
291 }
292 }
293 }
294
295 if ((endpointValue != nullptr) && (socketValue != nullptr) &&
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800296 *endpointValue == conn.url().path())
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800297 {
298 endpointObjectPath = &objectPath.str;
299 break;
300 }
301 }
302
303 if (objects.empty() || endpointObjectPath == nullptr)
304 {
Ed Tanous62598e32023-07-17 17:06:25 -0700305 BMCWEB_LOG_ERROR("Cannot find requested EndpointId");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800306 conn.close("Failed to match EndpointId");
307 return;
308 }
309
310 for (const auto& session : sessions)
311 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800312 if (session.second->getEndpointId() == conn.url().path())
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800313 {
Ed Tanous62598e32023-07-17 17:06:25 -0700314 BMCWEB_LOG_ERROR("Cannot open new connection - socket is in use");
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800315 conn.close("Slot is in use");
316 return;
317 }
318 }
319
320 // If the socket file exists (i.e. after bmcweb crash),
321 // we cannot reuse it.
322 std::remove((*socketValue).c_str());
323
324 sessions[&conn] = std::make_shared<NbdProxyServer>(
325 conn, *socketValue, *endpointValue, *endpointObjectPath);
326
327 sessions[&conn]->run();
328};
Ed Tanous8108fd32023-02-27 13:58:03 -0800329inline void onOpen(crow::websocket::Connection& conn)
330{
Ed Tanous62598e32023-07-17 17:06:25 -0700331 BMCWEB_LOG_DEBUG("nbd-proxy.onopen({})", logPtr(&conn));
Ed Tanous8108fd32023-02-27 13:58:03 -0800332
George Liu5eb468d2023-06-20 17:03:24 +0800333 sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia");
334 dbus::utility::getManagedObjects(
335 "xyz.openbmc_project.VirtualMedia", path,
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800336 [&conn](const boost::system::error_code& ec,
Ed Tanouse551b5f2023-02-27 14:19:07 -0800337 const dbus::utility::ManagedObjectType& objects) {
Ed Tanous2da6b8c2023-02-27 14:25:10 -0800338 afterGetManagedObjects(conn, ec, objects);
Patrick Williams5a39f772023-10-20 11:20:21 -0500339 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800340
341 // We need to wait for dbus and the websockets to hook up before data is
342 // sent/received. Tell the core to hold off messages until the sockets are
343 // up
344 conn.deferRead();
Ed Tanous8108fd32023-02-27 13:58:03 -0800345}
346
347inline void onClose(crow::websocket::Connection& conn,
348 const std::string& reason)
349{
Ed Tanous62598e32023-07-17 17:06:25 -0700350 BMCWEB_LOG_DEBUG("nbd-proxy.onclose(reason = '{}')", reason);
Ed Tanous8108fd32023-02-27 13:58:03 -0800351 auto session = sessions.find(&conn);
352 if (session == sessions.end())
353 {
Ed Tanous62598e32023-07-17 17:06:25 -0700354 BMCWEB_LOG_DEBUG("No session to close");
Ed Tanous8108fd32023-02-27 13:58:03 -0800355 return;
356 }
Ed Tanous8108fd32023-02-27 13:58:03 -0800357 // Remove reference to session in global map
358 sessions.erase(session);
359}
360
Ed Tanous863c1c22022-02-21 21:33:06 -0800361inline void onMessage(crow::websocket::Connection& conn, std::string_view data,
362 crow::websocket::MessageType /*type*/,
363 std::function<void()>&& whenComplete)
Ed Tanous8108fd32023-02-27 13:58:03 -0800364{
Ed Tanous62598e32023-07-17 17:06:25 -0700365 BMCWEB_LOG_DEBUG("nbd-proxy.onMessage(len = {})", data.size());
Ed Tanous863c1c22022-02-21 21:33:06 -0800366
Ed Tanous8108fd32023-02-27 13:58:03 -0800367 // Acquire proxy from sessions
368 auto session = sessions.find(&conn);
Ed Tanous863c1c22022-02-21 21:33:06 -0800369 if (session == sessions.end() || session->second == nullptr)
Ed Tanous8108fd32023-02-27 13:58:03 -0800370 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800371 whenComplete();
372 return;
Ed Tanous8108fd32023-02-27 13:58:03 -0800373 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800374
375 session->second->send(data, std::move(whenComplete));
Ed Tanous8108fd32023-02-27 13:58:03 -0800376}
377
Ed Tanous81ce6092020-12-17 16:54:55 +0000378inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200379{
380 BMCWEB_ROUTE(app, "/nbd/<str>")
381 .websocket()
Ed Tanous8108fd32023-02-27 13:58:03 -0800382 .onopen(onOpen)
383 .onclose(onClose)
Ed Tanous863c1c22022-02-21 21:33:06 -0800384 .onmessageex(onMessage);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200385}
386} // namespace nbd_proxy
387} // namespace crow