blob: ee45f3d93a392d6cc1939a9dc3559f4587f13e3d [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"
5#include "async_resp.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07006#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08007
Ed Tanous2daf6722018-08-23 11:27:23 -07008#include <sys/socket.h>
9
Ed Tanousd4d77e32020-08-18 00:07:28 -070010#include <boost/asio/local/stream_protocol.hpp>
Ninad Palsulef948d812023-05-25 16:53:01 -050011#include <boost/container/flat_map.hpp>
Gunnar Mills8b901d72024-05-03 16:58:32 -050012#include <boost/system/error_code.hpp>
13
14#include <array>
15#include <memory>
16#include <string>
17#include <string_view>
Ed Tanous2daf6722018-08-23 11:27:23 -070018
19namespace crow
20{
21namespace obmc_console
22{
23
Ninad Palsulef948d812023-05-25 16:53:01 -050024// Update this value each time we add new console route.
25static constexpr const uint maxSessions = 32;
Ed Tanous2daf6722018-08-23 11:27:23 -070026
Ninad Palsulef948d812023-05-25 16:53:01 -050027class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
Ed Tanous2daf6722018-08-23 11:27:23 -070028{
Ninad Palsulef948d812023-05-25 16:53:01 -050029 public:
30 ConsoleHandler(boost::asio::io_context& ioc,
31 crow::websocket::Connection& connIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040032 hostSocket(ioc), conn(connIn)
Ninad Palsulef948d812023-05-25 16:53:01 -050033 {}
34
35 ~ConsoleHandler() = default;
36
37 ConsoleHandler(const ConsoleHandler&) = delete;
38 ConsoleHandler(ConsoleHandler&&) = delete;
39 ConsoleHandler& operator=(const ConsoleHandler&) = delete;
40 ConsoleHandler& operator=(ConsoleHandler&&) = delete;
41
42 void doWrite()
Ed Tanous2daf6722018-08-23 11:27:23 -070043 {
Ninad Palsulef948d812023-05-25 16:53:01 -050044 if (doingWrite)
Ed Tanous002d39b2022-05-31 08:59:27 -070045 {
Ed Tanous62598e32023-07-17 17:06:25 -070046 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Ed Tanous002d39b2022-05-31 08:59:27 -070047 return;
48 }
Ninad Palsulef948d812023-05-25 16:53:01 -050049
50 if (inputBuffer.empty())
51 {
Ed Tanous62598e32023-07-17 17:06:25 -070052 BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out");
Ninad Palsulef948d812023-05-25 16:53:01 -050053 return;
54 }
55
56 doingWrite = true;
57 hostSocket.async_write_some(
58 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
59 [weak(weak_from_this())](const boost::beast::error_code& ec,
60 std::size_t bytesWritten) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040061 std::shared_ptr<ConsoleHandler> self = weak.lock();
62 if (self == nullptr)
63 {
64 return;
65 }
Ninad Palsulef948d812023-05-25 16:53:01 -050066
Patrick Williamsbd79bce2024-08-16 15:22:20 -040067 self->doingWrite = false;
68 self->inputBuffer.erase(0, bytesWritten);
Ninad Palsulef948d812023-05-25 16:53:01 -050069
Patrick Williamsbd79bce2024-08-16 15:22:20 -040070 if (ec == boost::asio::error::eof)
71 {
72 self->conn.close("Error in reading to host port");
73 return;
74 }
75 if (ec)
76 {
77 BMCWEB_LOG_ERROR("Error in host serial write {}",
78 ec.message());
79 return;
80 }
81 self->doWrite();
82 });
Ninad Palsulef948d812023-05-25 16:53:01 -050083 }
84
Ed Tanous099793f2023-05-31 08:54:26 -070085 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
86 {
87 std::shared_ptr<ConsoleHandler> self = weak.lock();
88 if (self == nullptr)
89 {
90 return;
91 }
92 self->doRead();
93 }
94
Ninad Palsulef948d812023-05-25 16:53:01 -050095 void doRead()
96 {
Ed Tanous62598e32023-07-17 17:06:25 -070097 BMCWEB_LOG_DEBUG("Reading from socket");
Ninad Palsulef948d812023-05-25 16:53:01 -050098 hostSocket.async_read_some(
Ed Tanous099793f2023-05-31 08:54:26 -070099 boost::asio::buffer(outputBuffer),
Ninad Palsulef948d812023-05-25 16:53:01 -0500100 [this, weakSelf(weak_from_this())](
101 const boost::system::error_code& ec, std::size_t bytesRead) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400102 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead);
103 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
104 if (self == nullptr)
105 {
106 return;
107 }
108 if (ec)
109 {
110 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
111 ec.message());
112 conn.close("Error connecting to host port");
113 return;
114 }
115 std::string_view payload(outputBuffer.data(), bytesRead);
116 self->conn.sendEx(
117 crow::websocket::MessageType::Binary, payload,
118 std::bind_front(afterSendEx, weak_from_this()));
119 });
Ninad Palsulef948d812023-05-25 16:53:01 -0500120 }
121
122 bool connect(int fd)
123 {
124 boost::system::error_code ec;
125 boost::asio::local::stream_protocol proto;
126
127 hostSocket.assign(proto, fd, ec);
128
Ed Tanous002d39b2022-05-31 08:59:27 -0700129 if (ec)
130 {
Ed Tanous62598e32023-07-17 17:06:25 -0700131 BMCWEB_LOG_ERROR(
132 "Failed to assign the DBUS socket Socket assign error: {}",
133 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500134 return false;
Ed Tanous002d39b2022-05-31 08:59:27 -0700135 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500136
137 conn.resumeRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700138 doWrite();
Ninad Palsulef948d812023-05-25 16:53:01 -0500139 doRead();
140 return true;
141 }
142
143 boost::asio::local::stream_protocol::socket hostSocket;
144
Ed Tanous099793f2023-05-31 08:54:26 -0700145 std::array<char, 4096> outputBuffer{};
Ninad Palsulef948d812023-05-25 16:53:01 -0500146
147 std::string inputBuffer;
148 bool doingWrite = false;
149 crow::websocket::Connection& conn;
150};
151
152using ObmcConsoleMap = boost::container::flat_map<
153 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
154 std::vector<std::pair<crow::websocket::Connection*,
155 std::shared_ptr<ConsoleHandler>>>>;
156
157inline ObmcConsoleMap& getConsoleHandlerMap()
158{
159 static ObmcConsoleMap map;
160 return map;
Ed Tanous2daf6722018-08-23 11:27:23 -0700161}
162
Ninad Palsulef948d812023-05-25 16:53:01 -0500163// Remove connection from the connection map and if connection map is empty
164// then remove the handler from handlers map.
165inline void onClose(crow::websocket::Connection& conn, const std::string& err)
Ed Tanous2daf6722018-08-23 11:27:23 -0700166{
Ed Tanous62598e32023-07-17 17:06:25 -0700167 BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
Ninad Palsulef948d812023-05-25 16:53:01 -0500168
169 auto iter = getConsoleHandlerMap().find(&conn);
170 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530171 {
Ed Tanous62598e32023-07-17 17:06:25 -0700172 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530173 return;
174 }
Ed Tanous62598e32023-07-17 17:06:25 -0700175 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530176
Ninad Palsulef948d812023-05-25 16:53:01 -0500177 // Removed last connection so remove the path
178 getConsoleHandlerMap().erase(iter);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500179}
180
181inline void connectConsoleSocket(crow::websocket::Connection& conn,
182 const boost::system::error_code& ec,
183 const sdbusplus::message::unix_fd& unixfd)
184{
Ed Tanous2daf6722018-08-23 11:27:23 -0700185 if (ec)
186 {
Ed Tanous62598e32023-07-17 17:06:25 -0700187 BMCWEB_LOG_ERROR(
188 "Failed to call console Connect() method DBUS error: {}",
189 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500190 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700191 return;
192 }
193
Ninad Palsulef948d812023-05-25 16:53:01 -0500194 // Look up the handler
195 auto iter = getConsoleHandlerMap().find(&conn);
196 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500197 {
Ed Tanous62598e32023-07-17 17:06:25 -0700198 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500199 return;
200 }
201
Ninad Palsulef948d812023-05-25 16:53:01 -0500202 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500203 if (fd == -1)
204 {
Ed Tanous7da633f2024-12-02 08:25:38 -0800205 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error");
Ninad Palsulef948d812023-05-25 16:53:01 -0500206 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500207 return;
208 }
209
Ed Tanous62598e32023-07-17 17:06:25 -0700210 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500211
Ninad Palsulef948d812023-05-25 16:53:01 -0500212 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500213 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500214 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500215 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500216 }
217}
218
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400219inline void processConsoleObject(
220 crow::websocket::Connection& conn, const std::string& consoleObjPath,
221 const boost::system::error_code& ec,
222 const ::dbus::utility::MapperGetObject& objInfo)
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500223{
224 // Look up the handler
225 auto iter = getConsoleHandlerMap().find(&conn);
226 if (iter == getConsoleHandlerMap().end())
227 {
Ed Tanous62598e32023-07-17 17:06:25 -0700228 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500229 return;
230 }
231
232 if (ec)
233 {
Ed Tanous62598e32023-07-17 17:06:25 -0700234 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
235 ec.message());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500236 conn.close("getDbusObject() for consoles failed.");
237 return;
238 }
239
240 const auto valueIface = objInfo.begin();
241 if (valueIface == objInfo.end())
242 {
Ed Tanous62598e32023-07-17 17:06:25 -0700243 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
244 objInfo.size());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500245 conn.close("getDbusObject() returned unexpected size");
246 return;
247 }
248
249 const std::string& consoleService = valueIface->first;
Ed Tanous62598e32023-07-17 17:06:25 -0700250 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
251 consoleObjPath);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500252 // Call Connect() method to get the unix FD
253 crow::connections::systemBus->async_method_call(
254 [&conn](const boost::system::error_code& ec1,
255 const sdbusplus::message::unix_fd& unixfd) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400256 connectConsoleSocket(conn, ec1, unixfd);
257 },
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500258 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
259 "Connect");
260}
261
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500262// Query consoles from DBUS and find the matching to the
263// rules string.
264inline void onOpen(crow::websocket::Connection& conn)
265{
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500266 std::string consoleLeaf;
267
Ed Tanous62598e32023-07-17 17:06:25 -0700268 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500269
Ninad Palsulef948d812023-05-25 16:53:01 -0500270 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500271 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500272 conn.close("Max sessions are already connected");
273 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500274 }
275
Ninad Palsulef948d812023-05-25 16:53:01 -0500276 std::shared_ptr<ConsoleHandler> handler =
277 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
278 getConsoleHandlerMap().emplace(&conn, handler);
279
280 conn.deferRead();
281
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500282 // Keep old path for backward compatibility
283 if (conn.url().path() == "/console0")
284 {
285 consoleLeaf = "default";
286 }
287 else
288 {
289 // Get the console id from console router path and prepare the console
290 // object path and console service.
291 consoleLeaf = conn.url().segments().back();
292 }
293 std::string consolePath =
294 sdbusplus::message::object_path("/xyz/openbmc_project/console") /
295 consoleLeaf;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500296
Ed Tanous62598e32023-07-17 17:06:25 -0700297 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
298 consolePath, conn.url().path());
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500299
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500300 // mapper call lambda
301 constexpr std::array<std::string_view, 1> interfaces = {
302 "xyz.openbmc_project.Console.Access"};
303
304 dbus::utility::getDbusObject(
305 consolePath, interfaces,
306 [&conn, consolePath](const boost::system::error_code& ec,
307 const ::dbus::utility::MapperGetObject& objInfo) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400308 processConsoleObject(conn, consolePath, ec, objInfo);
309 });
Ed Tanous2daf6722018-08-23 11:27:23 -0700310}
311
Ninad Palsulef948d812023-05-25 16:53:01 -0500312inline void onMessage(crow::websocket::Connection& conn,
313 const std::string& data, bool /*isBinary*/)
314{
315 auto handler = getConsoleHandlerMap().find(&conn);
316 if (handler == getConsoleHandlerMap().end())
317 {
Ed Tanous62598e32023-07-17 17:06:25 -0700318 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
Ninad Palsulef948d812023-05-25 16:53:01 -0500319 return;
320 }
321 handler->second->inputBuffer += data;
322 handler->second->doWrite();
323}
324
Ed Tanous23a21a12020-07-25 04:45:05 +0000325inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700326{
327 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500328 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700329 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500330 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500331 .onclose(onClose)
332 .onmessage(onMessage);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500333
334 BMCWEB_ROUTE(app, "/console/<str>")
335 .privileges({{"OpenBMCHostConsole"}})
336 .websocket()
337 .onopen(onOpen)
338 .onclose(onClose)
339 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700340}
341} // namespace obmc_console
342} // namespace crow