blob: 75cb2d2ac0cae3c00f8b80bd85cbbf008e5af6e3 [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"
20
Ed Tanousd43cd0c2020-09-30 20:46:53 -070021#include <boost/asio/buffer.hpp>
22#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>
25#include <boost/beast/core/multi_buffer.hpp>
26#include <boost/container/flat_map.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -070027#include <websocket.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020028
29namespace crow
30{
31
32namespace nbd_proxy
33{
34
35using boost::asio::local::stream_protocol;
36
37static constexpr auto 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 Tanous2c70f802020-09-28 14:29:23 -070047 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020048 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050049 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020050
Ed Tanous08e9fa92022-02-16 16:18:08 -080051 NbdProxyServer(const NbdProxyServer&) = delete;
52 NbdProxyServer(NbdProxyServer&&) = delete;
53 NbdProxyServer& operator=(const NbdProxyServer&) = delete;
54 NbdProxyServer& operator=(NbdProxyServer&&) = delete;
55
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020056 ~NbdProxyServer()
57 {
58 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020059 }
60
61 std::string getEndpointId() const
62 {
63 return endpointId;
64 }
65
66 void run()
67 {
Ed Tanous5e7e2dc2023-02-16 10:37:01 -080068 acceptor.async_accept([this, self(shared_from_this())](
69 const boost::system::error_code& ec,
70 stream_protocol::socket socket) {
Ed Tanous002d39b2022-05-31 08:59:27 -070071 if (ec)
72 {
73 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
74 << ec.message();
75 return;
76 }
77 if (peerSocket)
78 {
79 // Something is wrong - socket shouldn't be acquired at this
80 // point
81 BMCWEB_LOG_ERROR
82 << "Failed to open connection - socket already used";
83 return;
84 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020085
Ed Tanous002d39b2022-05-31 08:59:27 -070086 BMCWEB_LOG_DEBUG << "Connection opened";
87 peerSocket = std::move(socket);
88 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020089
Ed Tanous002d39b2022-05-31 08:59:27 -070090 // Trigger Write if any data was sent from server
91 // Initially this is negotiation chunk
92 doWrite();
93 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020094
Ed Tanous002d39b2022-05-31 08:59:27 -070095 auto mountHandler =
Ed Tanous5e7e2dc2023-02-16 10:37:01 -080096 [this, self(shared_from_this())](
97 const boost::system::error_code& ec, const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020098 if (ec)
99 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100100 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
101 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200102 connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200103 return;
104 }
105 };
106
107 crow::connections::systemBus->async_method_call(
108 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
109 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
110 }
111
Ed Tanous26ccae32023-02-16 10:28:44 -0800112 void send(std::string_view data)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200113 {
114 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
115 boost::asio::buffer(data));
116 ws2uxBuf.commit(data.size());
117 doWrite();
118 }
119
120 void close()
121 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100122 acceptor.close();
123 if (peerSocket)
124 {
125 BMCWEB_LOG_DEBUG << "peerSocket->close()";
126 peerSocket->close();
127 peerSocket.reset();
128 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
129 std::remove(socketId.c_str());
130 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200131 // The reference to session should exists until unmount is
132 // called
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800133 auto unmountHandler = [](const boost::system::error_code& ec) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200134 if (ec)
135 {
136 BMCWEB_LOG_ERROR << "DBus error: " << ec
137 << ", cannot call unmount method";
138 return;
139 }
140 };
141
142 crow::connections::systemBus->async_method_call(
143 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
144 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
145 }
146
147 private:
148 void doRead()
149 {
150 if (!peerSocket)
151 {
152 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
153 // Skip if UNIX socket is not created yet.
154 return;
155 }
156
157 // Trigger async read
158 peerSocket->async_read_some(
159 ux2wsBuf.prepare(nbdBufferSize),
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800160 [this, self(shared_from_this())](
161 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700162 if (ec)
163 {
164 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
165 << ec.message();
166 // UNIX socket has been closed by peer, best we can do is to
167 // break all connections
168 close();
169 return;
170 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200171
Ed Tanous002d39b2022-05-31 08:59:27 -0700172 // Fetch data from UNIX socket
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200173
Ed Tanous002d39b2022-05-31 08:59:27 -0700174 ux2wsBuf.commit(bytesRead);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200175
Ed Tanous002d39b2022-05-31 08:59:27 -0700176 // Paste it to WebSocket as binary
177 connection.sendBinary(
178 boost::beast::buffers_to_string(ux2wsBuf.data()));
179 ux2wsBuf.consume(bytesRead);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200180
Ed Tanous002d39b2022-05-31 08:59:27 -0700181 // Allow further reads
182 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200183 });
184 }
185
186 void doWrite()
187 {
188 if (!peerSocket)
189 {
190 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
191 // Skip if UNIX socket is not created yet. Collect data, and wait
192 // for nbd-client connection
193 return;
194 }
195
196 if (uxWriteInProgress)
197 {
198 BMCWEB_LOG_ERROR << "Write in progress";
199 return;
200 }
201
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800202 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200203 {
204 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
205 return;
206 }
207
208 uxWriteInProgress = true;
209 boost::asio::async_write(
210 *peerSocket, ws2uxBuf.data(),
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800211 [this, self(shared_from_this())](
212 const boost::system::error_code& ec, std::size_t bytesWritten) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700213 ws2uxBuf.consume(bytesWritten);
214 uxWriteInProgress = false;
215 if (ec)
216 {
217 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
218 << ec.message();
219 return;
220 }
221 // Retrigger doWrite if there is something in buffer
222 if (ws2uxBuf.size() > 0)
223 {
224 doWrite();
225 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200226 });
227 }
228
229 // Keeps UNIX socket endpoint file path
230 const std::string socketId;
231 const std::string endpointId;
232 const std::string path;
233
234 bool uxWriteInProgress = false;
235
236 // UNIX => WebSocket buffer
237 boost::beast::multi_buffer ux2wsBuf;
238
239 // WebSocket <= UNIX buffer
240 boost::beast::multi_buffer ws2uxBuf;
241
242 // Default acceptor for UNIX socket
243 stream_protocol::acceptor acceptor;
244
245 // The socket used to communicate with the client.
246 std::optional<stream_protocol::socket> peerSocket;
247
248 crow::websocket::Connection& connection;
249};
250
Ed Tanouscf9e4172022-12-21 09:30:16 -0800251using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
252 std::shared_ptr<NbdProxyServer>>;
253// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
254static SessionMap sessions;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200255
Ed Tanous8108fd32023-02-27 13:58:03 -0800256inline void onOpen(crow::websocket::Connection& conn)
257{
258 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
259
Ed Tanouse551b5f2023-02-27 14:19:07 -0800260 auto openHandler =
261 [&conn](const boost::system::error_code& ec2,
262 const dbus::utility::ManagedObjectType& objects) {
263 const std::string* socketValue = nullptr;
264 const std::string* endpointValue = nullptr;
265 const std::string* endpointObjectPath = nullptr;
266
267 if (ec2)
Ed Tanous8108fd32023-02-27 13:58:03 -0800268 {
Ed Tanouse551b5f2023-02-27 14:19:07 -0800269 BMCWEB_LOG_ERROR << "DBus error: " << ec2.message();
270 conn.close("Failed to create mount point");
Ed Tanous8108fd32023-02-27 13:58:03 -0800271 return;
272 }
273
Ed Tanouse551b5f2023-02-27 14:19:07 -0800274 for (const auto& [objectPath, interfaces] : objects)
Ed Tanous8108fd32023-02-27 13:58:03 -0800275 {
Ed Tanouse551b5f2023-02-27 14:19:07 -0800276 for (const auto& [interface, properties] : interfaces)
277 {
278 if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint")
279 {
280 continue;
281 }
282
283 for (const auto& [name, value] : properties)
284 {
285 if (name == "EndpointId")
286 {
287 endpointValue = std::get_if<std::string>(&value);
288
289 if (endpointValue == nullptr)
290 {
291 BMCWEB_LOG_ERROR
292 << "EndpointId property value is null";
293 }
294 }
295 if (name == "Socket")
296 {
297 socketValue = std::get_if<std::string>(&value);
298 if (socketValue == nullptr)
299 {
300 BMCWEB_LOG_ERROR << "Socket property value is null";
301 }
302 }
303 }
304 }
305
306 if ((endpointValue != nullptr) && (socketValue != nullptr) &&
307 *endpointValue == conn.req.target())
308 {
309 endpointObjectPath = &objectPath.str;
310 break;
311 }
Ed Tanous8108fd32023-02-27 13:58:03 -0800312 }
313
Ed Tanouse551b5f2023-02-27 14:19:07 -0800314 if (objects.empty() || endpointObjectPath == nullptr)
Ed Tanous8108fd32023-02-27 13:58:03 -0800315 {
Ed Tanouse551b5f2023-02-27 14:19:07 -0800316 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
317 conn.close("Failed to match EndpointId");
Ed Tanous8108fd32023-02-27 13:58:03 -0800318 return;
319 }
320
Ed Tanouse551b5f2023-02-27 14:19:07 -0800321 for (const auto& session : sessions)
322 {
323 if (session.second->getEndpointId() == conn.req.target())
Ed Tanous8108fd32023-02-27 13:58:03 -0800324 {
Ed Tanouse551b5f2023-02-27 14:19:07 -0800325 BMCWEB_LOG_ERROR
326 << "Cannot open new connection - socket is in use";
327 conn.close("Slot is in use");
Ed Tanous8108fd32023-02-27 13:58:03 -0800328 return;
329 }
Ed Tanouse551b5f2023-02-27 14:19:07 -0800330 }
Ed Tanous8108fd32023-02-27 13:58:03 -0800331
Ed Tanouse551b5f2023-02-27 14:19:07 -0800332 // If the socket file exists (i.e. after bmcweb crash),
333 // we cannot reuse it.
334 std::remove((*socketValue).c_str());
Ed Tanous8108fd32023-02-27 13:58:03 -0800335
Ed Tanouse551b5f2023-02-27 14:19:07 -0800336 sessions[&conn] = std::make_shared<NbdProxyServer>(
337 conn, *socketValue, *endpointValue, *endpointObjectPath);
Ed Tanous8108fd32023-02-27 13:58:03 -0800338
Ed Tanouse551b5f2023-02-27 14:19:07 -0800339 sessions[&conn]->run();
Ed Tanous8108fd32023-02-27 13:58:03 -0800340 };
Ed Tanous8108fd32023-02-27 13:58:03 -0800341 crow::connections::systemBus->async_method_call(
Ed Tanouse551b5f2023-02-27 14:19:07 -0800342 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
343 "/xyz/openbmc_project/VirtualMedia",
344 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Ed Tanous8108fd32023-02-27 13:58:03 -0800345}
346
347inline void onClose(crow::websocket::Connection& conn,
348 const std::string& reason)
349{
350 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason << "')";
351 auto session = sessions.find(&conn);
352 if (session == sessions.end())
353 {
354 BMCWEB_LOG_DEBUG << "No session to close";
355 return;
356 }
357 session->second->close();
358 // Remove reference to session in global map
359 sessions.erase(session);
360}
361
362inline void onMessage(crow::websocket::Connection& conn,
363 const std::string& data, bool /*isBinary*/)
364{
365 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length() << ")";
366 // Acquire proxy from sessions
367 auto session = sessions.find(&conn);
368 if (session != sessions.end())
369 {
370 if (session->second)
371 {
372 session->second->send(data);
373 return;
374 }
375 }
376}
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)
384 .onmessage(onMessage);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200385}
386} // namespace nbd_proxy
387} // namespace crow