blob: 81303e537f84b807f262f61f9d09637d9ad6fbf1 [file] [log] [blame]
Ed Tanous2daf6722018-08-23 11:27:23 -07001#pragma once
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08002#include "app.hpp"
3#include "async_resp.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07004#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005
Ed Tanous2daf6722018-08-23 11:27:23 -07006#include <sys/socket.h>
7
Ed Tanousd4d77e32020-08-18 00:07:28 -07008#include <boost/asio/local/stream_protocol.hpp>
Ninad Palsulef948d812023-05-25 16:53:01 -05009#include <boost/container/flat_map.hpp>
Gunnar Mills8b901d72024-05-03 16:58:32 -050010#include <boost/system/error_code.hpp>
11
12#include <array>
13#include <memory>
14#include <string>
15#include <string_view>
Ed Tanous2daf6722018-08-23 11:27:23 -070016
17namespace crow
18{
19namespace obmc_console
20{
21
Ninad Palsulef948d812023-05-25 16:53:01 -050022// Update this value each time we add new console route.
23static constexpr const uint maxSessions = 32;
Ed Tanous2daf6722018-08-23 11:27:23 -070024
Ninad Palsulef948d812023-05-25 16:53:01 -050025class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
Ed Tanous2daf6722018-08-23 11:27:23 -070026{
Ninad Palsulef948d812023-05-25 16:53:01 -050027 public:
28 ConsoleHandler(boost::asio::io_context& ioc,
29 crow::websocket::Connection& connIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040030 hostSocket(ioc), conn(connIn)
Ninad Palsulef948d812023-05-25 16:53:01 -050031 {}
32
33 ~ConsoleHandler() = default;
34
35 ConsoleHandler(const ConsoleHandler&) = delete;
36 ConsoleHandler(ConsoleHandler&&) = delete;
37 ConsoleHandler& operator=(const ConsoleHandler&) = delete;
38 ConsoleHandler& operator=(ConsoleHandler&&) = delete;
39
40 void doWrite()
Ed Tanous2daf6722018-08-23 11:27:23 -070041 {
Ninad Palsulef948d812023-05-25 16:53:01 -050042 if (doingWrite)
Ed Tanous002d39b2022-05-31 08:59:27 -070043 {
Ed Tanous62598e32023-07-17 17:06:25 -070044 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Ed Tanous002d39b2022-05-31 08:59:27 -070045 return;
46 }
Ninad Palsulef948d812023-05-25 16:53:01 -050047
48 if (inputBuffer.empty())
49 {
Ed Tanous62598e32023-07-17 17:06:25 -070050 BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out");
Ninad Palsulef948d812023-05-25 16:53:01 -050051 return;
52 }
53
54 doingWrite = true;
55 hostSocket.async_write_some(
56 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
57 [weak(weak_from_this())](const boost::beast::error_code& ec,
58 std::size_t bytesWritten) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040059 std::shared_ptr<ConsoleHandler> self = weak.lock();
60 if (self == nullptr)
61 {
62 return;
63 }
Ninad Palsulef948d812023-05-25 16:53:01 -050064
Patrick Williamsbd79bce2024-08-16 15:22:20 -040065 self->doingWrite = false;
66 self->inputBuffer.erase(0, bytesWritten);
Ninad Palsulef948d812023-05-25 16:53:01 -050067
Patrick Williamsbd79bce2024-08-16 15:22:20 -040068 if (ec == boost::asio::error::eof)
69 {
70 self->conn.close("Error in reading to host port");
71 return;
72 }
73 if (ec)
74 {
75 BMCWEB_LOG_ERROR("Error in host serial write {}",
76 ec.message());
77 return;
78 }
79 self->doWrite();
80 });
Ninad Palsulef948d812023-05-25 16:53:01 -050081 }
82
Ed Tanous099793f2023-05-31 08:54:26 -070083 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
84 {
85 std::shared_ptr<ConsoleHandler> self = weak.lock();
86 if (self == nullptr)
87 {
88 return;
89 }
90 self->doRead();
91 }
92
Ninad Palsulef948d812023-05-25 16:53:01 -050093 void doRead()
94 {
Ed Tanous62598e32023-07-17 17:06:25 -070095 BMCWEB_LOG_DEBUG("Reading from socket");
Ninad Palsulef948d812023-05-25 16:53:01 -050096 hostSocket.async_read_some(
Ed Tanous099793f2023-05-31 08:54:26 -070097 boost::asio::buffer(outputBuffer),
Ninad Palsulef948d812023-05-25 16:53:01 -050098 [this, weakSelf(weak_from_this())](
99 const boost::system::error_code& ec, std::size_t bytesRead) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400100 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead);
101 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
102 if (self == nullptr)
103 {
104 return;
105 }
106 if (ec)
107 {
108 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
109 ec.message());
110 conn.close("Error connecting to host port");
111 return;
112 }
113 std::string_view payload(outputBuffer.data(), bytesRead);
114 self->conn.sendEx(
115 crow::websocket::MessageType::Binary, payload,
116 std::bind_front(afterSendEx, weak_from_this()));
117 });
Ninad Palsulef948d812023-05-25 16:53:01 -0500118 }
119
120 bool connect(int fd)
121 {
122 boost::system::error_code ec;
123 boost::asio::local::stream_protocol proto;
124
125 hostSocket.assign(proto, fd, ec);
126
Ed Tanous002d39b2022-05-31 08:59:27 -0700127 if (ec)
128 {
Ed Tanous62598e32023-07-17 17:06:25 -0700129 BMCWEB_LOG_ERROR(
130 "Failed to assign the DBUS socket Socket assign error: {}",
131 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500132 return false;
Ed Tanous002d39b2022-05-31 08:59:27 -0700133 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500134
135 conn.resumeRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700136 doWrite();
Ninad Palsulef948d812023-05-25 16:53:01 -0500137 doRead();
138 return true;
139 }
140
141 boost::asio::local::stream_protocol::socket hostSocket;
142
Ed Tanous099793f2023-05-31 08:54:26 -0700143 std::array<char, 4096> outputBuffer{};
Ninad Palsulef948d812023-05-25 16:53:01 -0500144
145 std::string inputBuffer;
146 bool doingWrite = false;
147 crow::websocket::Connection& conn;
148};
149
150using ObmcConsoleMap = boost::container::flat_map<
151 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
152 std::vector<std::pair<crow::websocket::Connection*,
153 std::shared_ptr<ConsoleHandler>>>>;
154
155inline ObmcConsoleMap& getConsoleHandlerMap()
156{
157 static ObmcConsoleMap map;
158 return map;
Ed Tanous2daf6722018-08-23 11:27:23 -0700159}
160
Ninad Palsulef948d812023-05-25 16:53:01 -0500161// Remove connection from the connection map and if connection map is empty
162// then remove the handler from handlers map.
163inline void onClose(crow::websocket::Connection& conn, const std::string& err)
Ed Tanous2daf6722018-08-23 11:27:23 -0700164{
Ed Tanous62598e32023-07-17 17:06:25 -0700165 BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
Ninad Palsulef948d812023-05-25 16:53:01 -0500166
167 auto iter = getConsoleHandlerMap().find(&conn);
168 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530169 {
Ed Tanous62598e32023-07-17 17:06:25 -0700170 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530171 return;
172 }
Ed Tanous62598e32023-07-17 17:06:25 -0700173 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530174
Ninad Palsulef948d812023-05-25 16:53:01 -0500175 // Removed last connection so remove the path
176 getConsoleHandlerMap().erase(iter);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500177}
178
179inline void connectConsoleSocket(crow::websocket::Connection& conn,
180 const boost::system::error_code& ec,
181 const sdbusplus::message::unix_fd& unixfd)
182{
Ed Tanous2daf6722018-08-23 11:27:23 -0700183 if (ec)
184 {
Ed Tanous62598e32023-07-17 17:06:25 -0700185 BMCWEB_LOG_ERROR(
186 "Failed to call console Connect() method DBUS error: {}",
187 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500188 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700189 return;
190 }
191
Ninad Palsulef948d812023-05-25 16:53:01 -0500192 // Look up the handler
193 auto iter = getConsoleHandlerMap().find(&conn);
194 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500195 {
Ed Tanous62598e32023-07-17 17:06:25 -0700196 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500197 return;
198 }
199
Ninad Palsulef948d812023-05-25 16:53:01 -0500200 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500201 if (fd == -1)
202 {
Ed Tanous62598e32023-07-17 17:06:25 -0700203 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error: {}",
204 strerror(errno));
Ninad Palsulef948d812023-05-25 16:53:01 -0500205 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500206 return;
207 }
208
Ed Tanous62598e32023-07-17 17:06:25 -0700209 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500210
Ninad Palsulef948d812023-05-25 16:53:01 -0500211 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500212 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500213 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500214 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500215 }
216}
217
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400218inline void processConsoleObject(
219 crow::websocket::Connection& conn, const std::string& consoleObjPath,
220 const boost::system::error_code& ec,
221 const ::dbus::utility::MapperGetObject& objInfo)
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500222{
223 // Look up the handler
224 auto iter = getConsoleHandlerMap().find(&conn);
225 if (iter == getConsoleHandlerMap().end())
226 {
Ed Tanous62598e32023-07-17 17:06:25 -0700227 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500228 return;
229 }
230
231 if (ec)
232 {
Ed Tanous62598e32023-07-17 17:06:25 -0700233 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
234 ec.message());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500235 conn.close("getDbusObject() for consoles failed.");
236 return;
237 }
238
239 const auto valueIface = objInfo.begin();
240 if (valueIface == objInfo.end())
241 {
Ed Tanous62598e32023-07-17 17:06:25 -0700242 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
243 objInfo.size());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500244 conn.close("getDbusObject() returned unexpected size");
245 return;
246 }
247
248 const std::string& consoleService = valueIface->first;
Ed Tanous62598e32023-07-17 17:06:25 -0700249 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
250 consoleObjPath);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500251 // Call Connect() method to get the unix FD
252 crow::connections::systemBus->async_method_call(
253 [&conn](const boost::system::error_code& ec1,
254 const sdbusplus::message::unix_fd& unixfd) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400255 connectConsoleSocket(conn, ec1, unixfd);
256 },
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500257 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
258 "Connect");
259}
260
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500261// Query consoles from DBUS and find the matching to the
262// rules string.
263inline void onOpen(crow::websocket::Connection& conn)
264{
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500265 std::string consoleLeaf;
266
Ed Tanous62598e32023-07-17 17:06:25 -0700267 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500268
Ninad Palsulef948d812023-05-25 16:53:01 -0500269 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500270 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500271 conn.close("Max sessions are already connected");
272 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500273 }
274
Ninad Palsulef948d812023-05-25 16:53:01 -0500275 std::shared_ptr<ConsoleHandler> handler =
276 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
277 getConsoleHandlerMap().emplace(&conn, handler);
278
279 conn.deferRead();
280
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500281 // Keep old path for backward compatibility
282 if (conn.url().path() == "/console0")
283 {
284 consoleLeaf = "default";
285 }
286 else
287 {
288 // Get the console id from console router path and prepare the console
289 // object path and console service.
290 consoleLeaf = conn.url().segments().back();
291 }
292 std::string consolePath =
293 sdbusplus::message::object_path("/xyz/openbmc_project/console") /
294 consoleLeaf;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500295
Ed Tanous62598e32023-07-17 17:06:25 -0700296 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
297 consolePath, conn.url().path());
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500298
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500299 // mapper call lambda
300 constexpr std::array<std::string_view, 1> interfaces = {
301 "xyz.openbmc_project.Console.Access"};
302
303 dbus::utility::getDbusObject(
304 consolePath, interfaces,
305 [&conn, consolePath](const boost::system::error_code& ec,
306 const ::dbus::utility::MapperGetObject& objInfo) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400307 processConsoleObject(conn, consolePath, ec, objInfo);
308 });
Ed Tanous2daf6722018-08-23 11:27:23 -0700309}
310
Ninad Palsulef948d812023-05-25 16:53:01 -0500311inline void onMessage(crow::websocket::Connection& conn,
312 const std::string& data, bool /*isBinary*/)
313{
314 auto handler = getConsoleHandlerMap().find(&conn);
315 if (handler == getConsoleHandlerMap().end())
316 {
Ed Tanous62598e32023-07-17 17:06:25 -0700317 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
Ninad Palsulef948d812023-05-25 16:53:01 -0500318 return;
319 }
320 handler->second->inputBuffer += data;
321 handler->second->doWrite();
322}
323
Ed Tanous23a21a12020-07-25 04:45:05 +0000324inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700325{
326 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500327 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700328 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500329 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500330 .onclose(onClose)
331 .onmessage(onMessage);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500332
333 BMCWEB_ROUTE(app, "/console/<str>")
334 .privileges({{"OpenBMCHostConsole"}})
335 .websocket()
336 .onopen(onOpen)
337 .onclose(onClose)
338 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700339}
340} // namespace obmc_console
341} // namespace crow