blob: e94b6a938ae5dfc66537b0a7d1321b007b575348 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous2daf6722018-08-23 11:27:23 -07003#pragma once
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08004#include "app.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08005#include "dbus_singleton.hpp"
6#include "dbus_utility.hpp"
Ed Tanousd98a2f92025-02-06 17:36:31 -08007#include "io_context_singleton.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08008#include "logging.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07009#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080010
Ed Tanousd7857202025-01-28 15:32:26 -080011#include <sys/types.h>
12#include <unistd.h>
Ed Tanous2daf6722018-08-23 11:27:23 -070013
Ed Tanousd7857202025-01-28 15:32:26 -080014#include <boost/asio/buffer.hpp>
15#include <boost/asio/error.hpp>
16#include <boost/asio/io_context.hpp>
Ed Tanousd4d77e32020-08-18 00:07:28 -070017#include <boost/asio/local/stream_protocol.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080018#include <boost/beast/core/error.hpp>
Ninad Palsulef948d812023-05-25 16:53:01 -050019#include <boost/container/flat_map.hpp>
Gunnar Mills8b901d72024-05-03 16:58:32 -050020#include <boost/system/error_code.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080021#include <sdbusplus/message/native_types.hpp>
Gunnar Mills8b901d72024-05-03 16:58:32 -050022
23#include <array>
Ed Tanousd7857202025-01-28 15:32:26 -080024#include <cstddef>
25#include <functional>
Gunnar Mills8b901d72024-05-03 16:58:32 -050026#include <memory>
27#include <string>
28#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080029#include <utility>
30#include <vector>
Ed Tanous2daf6722018-08-23 11:27:23 -070031
32namespace crow
33{
34namespace obmc_console
35{
36
Ninad Palsulef948d812023-05-25 16:53:01 -050037// Update this value each time we add new console route.
38static constexpr const uint maxSessions = 32;
Ed Tanous2daf6722018-08-23 11:27:23 -070039
Ninad Palsulef948d812023-05-25 16:53:01 -050040class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
Ed Tanous2daf6722018-08-23 11:27:23 -070041{
Ninad Palsulef948d812023-05-25 16:53:01 -050042 public:
43 ConsoleHandler(boost::asio::io_context& ioc,
44 crow::websocket::Connection& connIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040045 hostSocket(ioc), conn(connIn)
Ninad Palsulef948d812023-05-25 16:53:01 -050046 {}
47
48 ~ConsoleHandler() = default;
49
50 ConsoleHandler(const ConsoleHandler&) = delete;
51 ConsoleHandler(ConsoleHandler&&) = delete;
52 ConsoleHandler& operator=(const ConsoleHandler&) = delete;
53 ConsoleHandler& operator=(ConsoleHandler&&) = delete;
54
55 void doWrite()
Ed Tanous2daf6722018-08-23 11:27:23 -070056 {
Ninad Palsulef948d812023-05-25 16:53:01 -050057 if (doingWrite)
Ed Tanous002d39b2022-05-31 08:59:27 -070058 {
Ed Tanous62598e32023-07-17 17:06:25 -070059 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Ed Tanous002d39b2022-05-31 08:59:27 -070060 return;
61 }
Ninad Palsulef948d812023-05-25 16:53:01 -050062
63 if (inputBuffer.empty())
64 {
Ed Tanous62598e32023-07-17 17:06:25 -070065 BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out");
Ninad Palsulef948d812023-05-25 16:53:01 -050066 return;
67 }
68
69 doingWrite = true;
70 hostSocket.async_write_some(
71 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
72 [weak(weak_from_this())](const boost::beast::error_code& ec,
73 std::size_t bytesWritten) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040074 std::shared_ptr<ConsoleHandler> self = weak.lock();
75 if (self == nullptr)
76 {
77 return;
78 }
Ninad Palsulef948d812023-05-25 16:53:01 -050079
Patrick Williamsbd79bce2024-08-16 15:22:20 -040080 self->doingWrite = false;
81 self->inputBuffer.erase(0, bytesWritten);
Ninad Palsulef948d812023-05-25 16:53:01 -050082
Patrick Williamsbd79bce2024-08-16 15:22:20 -040083 if (ec == boost::asio::error::eof)
84 {
85 self->conn.close("Error in reading to host port");
86 return;
87 }
88 if (ec)
89 {
90 BMCWEB_LOG_ERROR("Error in host serial write {}",
91 ec.message());
92 return;
93 }
94 self->doWrite();
95 });
Ninad Palsulef948d812023-05-25 16:53:01 -050096 }
97
Ed Tanous099793f2023-05-31 08:54:26 -070098 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
99 {
100 std::shared_ptr<ConsoleHandler> self = weak.lock();
101 if (self == nullptr)
102 {
103 return;
104 }
105 self->doRead();
106 }
107
Ninad Palsulef948d812023-05-25 16:53:01 -0500108 void doRead()
109 {
Ed Tanous62598e32023-07-17 17:06:25 -0700110 BMCWEB_LOG_DEBUG("Reading from socket");
Ninad Palsulef948d812023-05-25 16:53:01 -0500111 hostSocket.async_read_some(
Ed Tanous099793f2023-05-31 08:54:26 -0700112 boost::asio::buffer(outputBuffer),
Ninad Palsulef948d812023-05-25 16:53:01 -0500113 [this, weakSelf(weak_from_this())](
114 const boost::system::error_code& ec, std::size_t bytesRead) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400115 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead);
116 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
117 if (self == nullptr)
118 {
119 return;
120 }
121 if (ec)
122 {
123 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
124 ec.message());
125 conn.close("Error connecting to host port");
126 return;
127 }
128 std::string_view payload(outputBuffer.data(), bytesRead);
129 self->conn.sendEx(
130 crow::websocket::MessageType::Binary, payload,
131 std::bind_front(afterSendEx, weak_from_this()));
132 });
Ninad Palsulef948d812023-05-25 16:53:01 -0500133 }
134
135 bool connect(int fd)
136 {
137 boost::system::error_code ec;
138 boost::asio::local::stream_protocol proto;
139
140 hostSocket.assign(proto, fd, ec);
141
Ed Tanous002d39b2022-05-31 08:59:27 -0700142 if (ec)
143 {
Ed Tanous62598e32023-07-17 17:06:25 -0700144 BMCWEB_LOG_ERROR(
145 "Failed to assign the DBUS socket Socket assign error: {}",
146 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500147 return false;
Ed Tanous002d39b2022-05-31 08:59:27 -0700148 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500149
150 conn.resumeRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700151 doWrite();
Ninad Palsulef948d812023-05-25 16:53:01 -0500152 doRead();
153 return true;
154 }
155
156 boost::asio::local::stream_protocol::socket hostSocket;
157
Ed Tanous099793f2023-05-31 08:54:26 -0700158 std::array<char, 4096> outputBuffer{};
Ninad Palsulef948d812023-05-25 16:53:01 -0500159
160 std::string inputBuffer;
161 bool doingWrite = false;
162 crow::websocket::Connection& conn;
163};
164
165using ObmcConsoleMap = boost::container::flat_map<
166 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
167 std::vector<std::pair<crow::websocket::Connection*,
168 std::shared_ptr<ConsoleHandler>>>>;
169
170inline ObmcConsoleMap& getConsoleHandlerMap()
171{
172 static ObmcConsoleMap map;
173 return map;
Ed Tanous2daf6722018-08-23 11:27:23 -0700174}
175
Ninad Palsulef948d812023-05-25 16:53:01 -0500176// Remove connection from the connection map and if connection map is empty
177// then remove the handler from handlers map.
178inline void onClose(crow::websocket::Connection& conn, const std::string& err)
Ed Tanous2daf6722018-08-23 11:27:23 -0700179{
Ed Tanous62598e32023-07-17 17:06:25 -0700180 BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
Ninad Palsulef948d812023-05-25 16:53:01 -0500181
182 auto iter = getConsoleHandlerMap().find(&conn);
183 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530184 {
Ed Tanous62598e32023-07-17 17:06:25 -0700185 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530186 return;
187 }
Ed Tanous62598e32023-07-17 17:06:25 -0700188 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530189
Ninad Palsulef948d812023-05-25 16:53:01 -0500190 // Removed last connection so remove the path
191 getConsoleHandlerMap().erase(iter);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500192}
193
194inline void connectConsoleSocket(crow::websocket::Connection& conn,
195 const boost::system::error_code& ec,
196 const sdbusplus::message::unix_fd& unixfd)
197{
Ed Tanous2daf6722018-08-23 11:27:23 -0700198 if (ec)
199 {
Ed Tanous62598e32023-07-17 17:06:25 -0700200 BMCWEB_LOG_ERROR(
201 "Failed to call console Connect() method DBUS error: {}",
202 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500203 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700204 return;
205 }
206
Ninad Palsulef948d812023-05-25 16:53:01 -0500207 // Look up the handler
208 auto iter = getConsoleHandlerMap().find(&conn);
209 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500210 {
Ed Tanous62598e32023-07-17 17:06:25 -0700211 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500212 return;
213 }
214
Ninad Palsulef948d812023-05-25 16:53:01 -0500215 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500216 if (fd == -1)
217 {
Ed Tanous7da633f2024-12-02 08:25:38 -0800218 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error");
Ninad Palsulef948d812023-05-25 16:53:01 -0500219 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500220 return;
221 }
222
Ed Tanous62598e32023-07-17 17:06:25 -0700223 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500224
Ninad Palsulef948d812023-05-25 16:53:01 -0500225 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500226 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500227 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500228 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500229 }
230}
231
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400232inline void processConsoleObject(
233 crow::websocket::Connection& conn, const std::string& consoleObjPath,
234 const boost::system::error_code& ec,
235 const ::dbus::utility::MapperGetObject& objInfo)
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500236{
237 // Look up the handler
238 auto iter = getConsoleHandlerMap().find(&conn);
239 if (iter == getConsoleHandlerMap().end())
240 {
Ed Tanous62598e32023-07-17 17:06:25 -0700241 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500242 return;
243 }
244
245 if (ec)
246 {
Ed Tanous62598e32023-07-17 17:06:25 -0700247 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
248 ec.message());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500249 conn.close("getDbusObject() for consoles failed.");
250 return;
251 }
252
253 const auto valueIface = objInfo.begin();
254 if (valueIface == objInfo.end())
255 {
Ed Tanous62598e32023-07-17 17:06:25 -0700256 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
257 objInfo.size());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500258 conn.close("getDbusObject() returned unexpected size");
259 return;
260 }
261
262 const std::string& consoleService = valueIface->first;
Ed Tanous62598e32023-07-17 17:06:25 -0700263 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
264 consoleObjPath);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500265 // Call Connect() method to get the unix FD
266 crow::connections::systemBus->async_method_call(
267 [&conn](const boost::system::error_code& ec1,
268 const sdbusplus::message::unix_fd& unixfd) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400269 connectConsoleSocket(conn, ec1, unixfd);
270 },
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500271 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
272 "Connect");
273}
274
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500275// Query consoles from DBUS and find the matching to the
276// rules string.
277inline void onOpen(crow::websocket::Connection& conn)
278{
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500279 std::string consoleLeaf;
280
Ed Tanous62598e32023-07-17 17:06:25 -0700281 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500282
Ninad Palsulef948d812023-05-25 16:53:01 -0500283 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500284 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500285 conn.close("Max sessions are already connected");
286 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500287 }
288
Ninad Palsulef948d812023-05-25 16:53:01 -0500289 std::shared_ptr<ConsoleHandler> handler =
Ed Tanousd98a2f92025-02-06 17:36:31 -0800290 std::make_shared<ConsoleHandler>(getIoContext(), conn);
Ninad Palsulef948d812023-05-25 16:53:01 -0500291 getConsoleHandlerMap().emplace(&conn, handler);
292
293 conn.deferRead();
294
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500295 // Keep old path for backward compatibility
296 if (conn.url().path() == "/console0")
297 {
298 consoleLeaf = "default";
299 }
300 else
301 {
302 // Get the console id from console router path and prepare the console
303 // object path and console service.
304 consoleLeaf = conn.url().segments().back();
305 }
306 std::string consolePath =
307 sdbusplus::message::object_path("/xyz/openbmc_project/console") /
308 consoleLeaf;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500309
Ed Tanous62598e32023-07-17 17:06:25 -0700310 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
311 consolePath, conn.url().path());
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500312
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500313 // mapper call lambda
314 constexpr std::array<std::string_view, 1> interfaces = {
315 "xyz.openbmc_project.Console.Access"};
316
317 dbus::utility::getDbusObject(
318 consolePath, interfaces,
319 [&conn, consolePath](const boost::system::error_code& ec,
320 const ::dbus::utility::MapperGetObject& objInfo) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400321 processConsoleObject(conn, consolePath, ec, objInfo);
322 });
Ed Tanous2daf6722018-08-23 11:27:23 -0700323}
324
Ninad Palsulef948d812023-05-25 16:53:01 -0500325inline void onMessage(crow::websocket::Connection& conn,
326 const std::string& data, bool /*isBinary*/)
327{
328 auto handler = getConsoleHandlerMap().find(&conn);
329 if (handler == getConsoleHandlerMap().end())
330 {
Ed Tanous62598e32023-07-17 17:06:25 -0700331 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
Ninad Palsulef948d812023-05-25 16:53:01 -0500332 return;
333 }
334 handler->second->inputBuffer += data;
335 handler->second->doWrite();
336}
337
Ed Tanous23a21a12020-07-25 04:45:05 +0000338inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700339{
340 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500341 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700342 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500343 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500344 .onclose(onClose)
345 .onmessage(onMessage);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500346
347 BMCWEB_ROUTE(app, "/console/<str>")
348 .privileges({{"OpenBMCHostConsole"}})
349 .websocket()
350 .onopen(onOpen)
351 .onclose(onClose)
352 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700353}
354} // namespace obmc_console
355} // namespace crow