blob: 2dd3edbf510b601e13315238e66020f56c140f38 [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>
Ed Tanous2daf6722018-08-23 11:27:23 -070010
11namespace crow
12{
13namespace obmc_console
14{
15
Ninad Palsulef948d812023-05-25 16:53:01 -050016// Update this value each time we add new console route.
17static constexpr const uint maxSessions = 32;
Ed Tanous2daf6722018-08-23 11:27:23 -070018
Ninad Palsulef948d812023-05-25 16:53:01 -050019class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
Ed Tanous2daf6722018-08-23 11:27:23 -070020{
Ninad Palsulef948d812023-05-25 16:53:01 -050021 public:
22 ConsoleHandler(boost::asio::io_context& ioc,
23 crow::websocket::Connection& connIn) :
24 hostSocket(ioc),
25 conn(connIn)
26 {}
27
28 ~ConsoleHandler() = default;
29
30 ConsoleHandler(const ConsoleHandler&) = delete;
31 ConsoleHandler(ConsoleHandler&&) = delete;
32 ConsoleHandler& operator=(const ConsoleHandler&) = delete;
33 ConsoleHandler& operator=(ConsoleHandler&&) = delete;
34
35 void doWrite()
Ed Tanous2daf6722018-08-23 11:27:23 -070036 {
Ninad Palsulef948d812023-05-25 16:53:01 -050037 if (doingWrite)
Ed Tanous002d39b2022-05-31 08:59:27 -070038 {
Ed Tanous62598e32023-07-17 17:06:25 -070039 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Ed Tanous002d39b2022-05-31 08:59:27 -070040 return;
41 }
Ninad Palsulef948d812023-05-25 16:53:01 -050042
43 if (inputBuffer.empty())
44 {
Ed Tanous62598e32023-07-17 17:06:25 -070045 BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out");
Ninad Palsulef948d812023-05-25 16:53:01 -050046 return;
47 }
48
49 doingWrite = true;
50 hostSocket.async_write_some(
51 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
52 [weak(weak_from_this())](const boost::beast::error_code& ec,
53 std::size_t bytesWritten) {
54 std::shared_ptr<ConsoleHandler> self = weak.lock();
55 if (self == nullptr)
56 {
57 return;
58 }
59
60 self->doingWrite = false;
61 self->inputBuffer.erase(0, bytesWritten);
62
63 if (ec == boost::asio::error::eof)
64 {
65 self->conn.close("Error in reading to host port");
66 return;
67 }
68 if (ec)
69 {
Ed Tanous62598e32023-07-17 17:06:25 -070070 BMCWEB_LOG_ERROR("Error in host serial write {}", ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -050071 return;
72 }
73 self->doWrite();
74 });
75 }
76
Ed Tanous099793f2023-05-31 08:54:26 -070077 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
78 {
79 std::shared_ptr<ConsoleHandler> self = weak.lock();
80 if (self == nullptr)
81 {
82 return;
83 }
84 self->doRead();
85 }
86
Ninad Palsulef948d812023-05-25 16:53:01 -050087 void doRead()
88 {
Ed Tanous62598e32023-07-17 17:06:25 -070089 BMCWEB_LOG_DEBUG("Reading from socket");
Ninad Palsulef948d812023-05-25 16:53:01 -050090 hostSocket.async_read_some(
Ed Tanous099793f2023-05-31 08:54:26 -070091 boost::asio::buffer(outputBuffer),
Ninad Palsulef948d812023-05-25 16:53:01 -050092 [this, weakSelf(weak_from_this())](
93 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous62598e32023-07-17 17:06:25 -070094 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead);
Ninad Palsulef948d812023-05-25 16:53:01 -050095 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
96 if (self == nullptr)
97 {
98 return;
99 }
100 if (ec)
101 {
Ed Tanous62598e32023-07-17 17:06:25 -0700102 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
103 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500104 conn.close("Error connecting to host port");
105 return;
106 }
Ed Tanous099793f2023-05-31 08:54:26 -0700107 std::string_view payload(outputBuffer.data(), bytesRead);
108 self->conn.sendEx(crow::websocket::MessageType::Binary, payload,
109 std::bind_front(afterSendEx, weak_from_this()));
Ninad Palsulef948d812023-05-25 16:53:01 -0500110 });
111 }
112
113 bool connect(int fd)
114 {
115 boost::system::error_code ec;
116 boost::asio::local::stream_protocol proto;
117
118 hostSocket.assign(proto, fd, ec);
119
Ed Tanous002d39b2022-05-31 08:59:27 -0700120 if (ec)
121 {
Ed Tanous62598e32023-07-17 17:06:25 -0700122 BMCWEB_LOG_ERROR(
123 "Failed to assign the DBUS socket Socket assign error: {}",
124 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500125 return false;
Ed Tanous002d39b2022-05-31 08:59:27 -0700126 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500127
128 conn.resumeRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700129 doWrite();
Ninad Palsulef948d812023-05-25 16:53:01 -0500130 doRead();
131 return true;
132 }
133
134 boost::asio::local::stream_protocol::socket hostSocket;
135
Ed Tanous099793f2023-05-31 08:54:26 -0700136 std::array<char, 4096> outputBuffer{};
Ninad Palsulef948d812023-05-25 16:53:01 -0500137
138 std::string inputBuffer;
139 bool doingWrite = false;
140 crow::websocket::Connection& conn;
141};
142
143using ObmcConsoleMap = boost::container::flat_map<
144 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
145 std::vector<std::pair<crow::websocket::Connection*,
146 std::shared_ptr<ConsoleHandler>>>>;
147
148inline ObmcConsoleMap& getConsoleHandlerMap()
149{
150 static ObmcConsoleMap map;
151 return map;
Ed Tanous2daf6722018-08-23 11:27:23 -0700152}
153
Ninad Palsulef948d812023-05-25 16:53:01 -0500154// Remove connection from the connection map and if connection map is empty
155// then remove the handler from handlers map.
156inline void onClose(crow::websocket::Connection& conn, const std::string& err)
Ed Tanous2daf6722018-08-23 11:27:23 -0700157{
Ed Tanous62598e32023-07-17 17:06:25 -0700158 BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
Ninad Palsulef948d812023-05-25 16:53:01 -0500159
160 auto iter = getConsoleHandlerMap().find(&conn);
161 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530162 {
Ed Tanous62598e32023-07-17 17:06:25 -0700163 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530164 return;
165 }
Ed Tanous62598e32023-07-17 17:06:25 -0700166 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530167
Ninad Palsulef948d812023-05-25 16:53:01 -0500168 // Removed last connection so remove the path
169 getConsoleHandlerMap().erase(iter);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500170}
171
172inline void connectConsoleSocket(crow::websocket::Connection& conn,
173 const boost::system::error_code& ec,
174 const sdbusplus::message::unix_fd& unixfd)
175{
Ed Tanous2daf6722018-08-23 11:27:23 -0700176 if (ec)
177 {
Ed Tanous62598e32023-07-17 17:06:25 -0700178 BMCWEB_LOG_ERROR(
179 "Failed to call console Connect() method DBUS error: {}",
180 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500181 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700182 return;
183 }
184
Ninad Palsulef948d812023-05-25 16:53:01 -0500185 // Look up the handler
186 auto iter = getConsoleHandlerMap().find(&conn);
187 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500188 {
Ed Tanous62598e32023-07-17 17:06:25 -0700189 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500190 return;
191 }
192
Ninad Palsulef948d812023-05-25 16:53:01 -0500193 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500194 if (fd == -1)
195 {
Ed Tanous62598e32023-07-17 17:06:25 -0700196 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error: {}",
197 strerror(errno));
Ninad Palsulef948d812023-05-25 16:53:01 -0500198 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500199 return;
200 }
201
Ed Tanous62598e32023-07-17 17:06:25 -0700202 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500203
Ninad Palsulef948d812023-05-25 16:53:01 -0500204 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500205 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500206 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500207 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500208 }
209}
210
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500211inline void
212 processConsoleObject(crow::websocket::Connection& conn,
213 const std::string& consoleObjPath,
214 const boost::system::error_code& ec,
215 const ::dbus::utility::MapperGetObject& objInfo)
216{
217 // Look up the handler
218 auto iter = getConsoleHandlerMap().find(&conn);
219 if (iter == getConsoleHandlerMap().end())
220 {
Ed Tanous62598e32023-07-17 17:06:25 -0700221 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500222 return;
223 }
224
225 if (ec)
226 {
Ed Tanous62598e32023-07-17 17:06:25 -0700227 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
228 ec.message());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500229 conn.close("getDbusObject() for consoles failed.");
230 return;
231 }
232
233 const auto valueIface = objInfo.begin();
234 if (valueIface == objInfo.end())
235 {
Ed Tanous62598e32023-07-17 17:06:25 -0700236 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
237 objInfo.size());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500238 conn.close("getDbusObject() returned unexpected size");
239 return;
240 }
241
242 const std::string& consoleService = valueIface->first;
Ed Tanous62598e32023-07-17 17:06:25 -0700243 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
244 consoleObjPath);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500245 // Call Connect() method to get the unix FD
246 crow::connections::systemBus->async_method_call(
247 [&conn](const boost::system::error_code& ec1,
248 const sdbusplus::message::unix_fd& unixfd) {
249 connectConsoleSocket(conn, ec1, unixfd);
250 },
251 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
252 "Connect");
253}
254
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500255// Query consoles from DBUS and find the matching to the
256// rules string.
257inline void onOpen(crow::websocket::Connection& conn)
258{
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500259 std::string consoleLeaf;
260
Ed Tanous62598e32023-07-17 17:06:25 -0700261 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500262
Ninad Palsulef948d812023-05-25 16:53:01 -0500263 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500264 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500265 conn.close("Max sessions are already connected");
266 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500267 }
268
Ninad Palsulef948d812023-05-25 16:53:01 -0500269 std::shared_ptr<ConsoleHandler> handler =
270 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
271 getConsoleHandlerMap().emplace(&conn, handler);
272
273 conn.deferRead();
274
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500275 // Keep old path for backward compatibility
276 if (conn.url().path() == "/console0")
277 {
278 consoleLeaf = "default";
279 }
280 else
281 {
282 // Get the console id from console router path and prepare the console
283 // object path and console service.
284 consoleLeaf = conn.url().segments().back();
285 }
286 std::string consolePath =
287 sdbusplus::message::object_path("/xyz/openbmc_project/console") /
288 consoleLeaf;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500289
Ed Tanous62598e32023-07-17 17:06:25 -0700290 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
291 consolePath, conn.url().path());
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500292
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500293 // mapper call lambda
294 constexpr std::array<std::string_view, 1> interfaces = {
295 "xyz.openbmc_project.Console.Access"};
296
297 dbus::utility::getDbusObject(
298 consolePath, interfaces,
299 [&conn, consolePath](const boost::system::error_code& ec,
300 const ::dbus::utility::MapperGetObject& objInfo) {
301 processConsoleObject(conn, consolePath, ec, objInfo);
302 });
Ed Tanous2daf6722018-08-23 11:27:23 -0700303}
304
Ninad Palsulef948d812023-05-25 16:53:01 -0500305inline void onMessage(crow::websocket::Connection& conn,
306 const std::string& data, bool /*isBinary*/)
307{
308 auto handler = getConsoleHandlerMap().find(&conn);
309 if (handler == getConsoleHandlerMap().end())
310 {
Ed Tanous62598e32023-07-17 17:06:25 -0700311 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
Ninad Palsulef948d812023-05-25 16:53:01 -0500312 return;
313 }
314 handler->second->inputBuffer += data;
315 handler->second->doWrite();
316}
317
Ed Tanous23a21a12020-07-25 04:45:05 +0000318inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700319{
320 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500321 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700322 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500323 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500324 .onclose(onClose)
325 .onmessage(onMessage);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500326
327 BMCWEB_ROUTE(app, "/console/<str>")
328 .privileges({{"OpenBMCHostConsole"}})
329 .websocket()
330 .onopen(onOpen)
331 .onclose(onClose)
332 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700333}
334} // namespace obmc_console
335} // namespace crow