blob: f9b978d1f53aa48e22f9f92db2ae0e02a761a366 [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 {
Ninad Palsulef948d812023-05-25 16:53:01 -050039 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 {
45 BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out";
46 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 {
70 BMCWEB_LOG_ERROR << "Error in host serial write "
71 << ec.message();
72 return;
73 }
74 self->doWrite();
75 });
76 }
77
Ed Tanous099793f2023-05-31 08:54:26 -070078 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
79 {
80 std::shared_ptr<ConsoleHandler> self = weak.lock();
81 if (self == nullptr)
82 {
83 return;
84 }
85 self->doRead();
86 }
87
Ninad Palsulef948d812023-05-25 16:53:01 -050088 void doRead()
89 {
Ninad Palsulef948d812023-05-25 16:53:01 -050090 BMCWEB_LOG_DEBUG << "Reading from socket";
91 hostSocket.async_read_some(
Ed Tanous099793f2023-05-31 08:54:26 -070092 boost::asio::buffer(outputBuffer),
Ninad Palsulef948d812023-05-25 16:53:01 -050093 [this, weakSelf(weak_from_this())](
94 const boost::system::error_code& ec, std::size_t bytesRead) {
95 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes";
96 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
97 if (self == nullptr)
98 {
99 return;
100 }
101 if (ec)
102 {
103 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: "
104 << ec.message();
105 conn.close("Error connecting to host port");
106 return;
107 }
Ed Tanous099793f2023-05-31 08:54:26 -0700108 std::string_view payload(outputBuffer.data(), bytesRead);
109 self->conn.sendEx(crow::websocket::MessageType::Binary, payload,
110 std::bind_front(afterSendEx, weak_from_this()));
Ninad Palsulef948d812023-05-25 16:53:01 -0500111 });
112 }
113
114 bool connect(int fd)
115 {
116 boost::system::error_code ec;
117 boost::asio::local::stream_protocol proto;
118
119 hostSocket.assign(proto, fd, ec);
120
Ed Tanous002d39b2022-05-31 08:59:27 -0700121 if (ec)
122 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500123 BMCWEB_LOG_ERROR << "Failed to assign the DBUS socket"
124 << " Socket assign error: " << ec.message();
125 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{
Ninad Palsulef948d812023-05-25 16:53:01 -0500158 BMCWEB_LOG_INFO << "Closing websocket. Reason: " << err;
159
160 auto iter = getConsoleHandlerMap().find(&conn);
161 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530162 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500163 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn;
AppaRao Puli5238bd32020-11-17 11:03:11 +0530164 return;
165 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500166 BMCWEB_LOG_DEBUG << "Remove connection " << &conn << " from obmc console";
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 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500178 BMCWEB_LOG_ERROR << "Failed to call console Connect() method"
179 << " DBUS error: " << ec.message();
Ninad Palsulef948d812023-05-25 16:53:01 -0500180 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700181 return;
182 }
183
Ninad Palsulef948d812023-05-25 16:53:01 -0500184 // Look up the handler
185 auto iter = getConsoleHandlerMap().find(&conn);
186 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500187 {
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500188 BMCWEB_LOG_ERROR << "Connection was already closed";
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500189 return;
190 }
191
Ninad Palsulef948d812023-05-25 16:53:01 -0500192 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500193 if (fd == -1)
194 {
195 BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd"
196 << " error: " << strerror(errno);
Ninad Palsulef948d812023-05-25 16:53:01 -0500197 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500198 return;
199 }
200
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500201 BMCWEB_LOG_DEBUG << "Console unix FD: " << unixfd << " duped FD: " << fd;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500202
Ninad Palsulef948d812023-05-25 16:53:01 -0500203 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500204 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500205 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500206 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500207 }
208}
209
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500210inline void
211 processConsoleObject(crow::websocket::Connection& conn,
212 const std::string& consoleObjPath,
213 const boost::system::error_code& ec,
214 const ::dbus::utility::MapperGetObject& objInfo)
215{
216 // Look up the handler
217 auto iter = getConsoleHandlerMap().find(&conn);
218 if (iter == getConsoleHandlerMap().end())
219 {
220 BMCWEB_LOG_ERROR << "Connection was already closed";
221 return;
222 }
223
224 if (ec)
225 {
226 BMCWEB_LOG_WARNING << "getDbusObject() for consoles failed. DBUS error:"
227 << ec.message();
228 conn.close("getDbusObject() for consoles failed.");
229 return;
230 }
231
232 const auto valueIface = objInfo.begin();
233 if (valueIface == objInfo.end())
234 {
235 BMCWEB_LOG_WARNING << "getDbusObject() returned unexpected size: "
236 << objInfo.size();
237 conn.close("getDbusObject() returned unexpected size");
238 return;
239 }
240
241 const std::string& consoleService = valueIface->first;
242 BMCWEB_LOG_DEBUG << "Looking up unixFD for Service " << consoleService
243 << " Path " << consoleObjPath;
244 // Call Connect() method to get the unix FD
245 crow::connections::systemBus->async_method_call(
246 [&conn](const boost::system::error_code& ec1,
247 const sdbusplus::message::unix_fd& unixfd) {
248 connectConsoleSocket(conn, ec1, unixfd);
249 },
250 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
251 "Connect");
252}
253
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500254// Query consoles from DBUS and find the matching to the
255// rules string.
256inline void onOpen(crow::websocket::Connection& conn)
257{
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500258 std::string consoleLeaf;
259
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500260 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
261
Ninad Palsulef948d812023-05-25 16:53:01 -0500262 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500263 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500264 conn.close("Max sessions are already connected");
265 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500266 }
267
Ninad Palsulef948d812023-05-25 16:53:01 -0500268 std::shared_ptr<ConsoleHandler> handler =
269 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
270 getConsoleHandlerMap().emplace(&conn, handler);
271
272 conn.deferRead();
273
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500274 // Keep old path for backward compatibility
275 if (conn.url().path() == "/console0")
276 {
277 consoleLeaf = "default";
278 }
279 else
280 {
281 // Get the console id from console router path and prepare the console
282 // object path and console service.
283 consoleLeaf = conn.url().segments().back();
284 }
285 std::string consolePath =
286 sdbusplus::message::object_path("/xyz/openbmc_project/console") /
287 consoleLeaf;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500288
289 BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500290 << " Request target = " << conn.url().path();
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500291
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500292 // mapper call lambda
293 constexpr std::array<std::string_view, 1> interfaces = {
294 "xyz.openbmc_project.Console.Access"};
295
296 dbus::utility::getDbusObject(
297 consolePath, interfaces,
298 [&conn, consolePath](const boost::system::error_code& ec,
299 const ::dbus::utility::MapperGetObject& objInfo) {
300 processConsoleObject(conn, consolePath, ec, objInfo);
301 });
Ed Tanous2daf6722018-08-23 11:27:23 -0700302}
303
Ninad Palsulef948d812023-05-25 16:53:01 -0500304inline void onMessage(crow::websocket::Connection& conn,
305 const std::string& data, bool /*isBinary*/)
306{
307 auto handler = getConsoleHandlerMap().find(&conn);
308 if (handler == getConsoleHandlerMap().end())
309 {
310 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn;
311 return;
312 }
313 handler->second->inputBuffer += data;
314 handler->second->doWrite();
315}
316
Ed Tanous23a21a12020-07-25 04:45:05 +0000317inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700318{
319 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500320 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700321 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500322 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500323 .onclose(onClose)
324 .onmessage(onMessage);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500325
326 BMCWEB_ROUTE(app, "/console/<str>")
327 .privileges({{"OpenBMCHostConsole"}})
328 .websocket()
329 .onopen(onOpen)
330 .onclose(onClose)
331 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700332}
333} // namespace obmc_console
334} // namespace crow