blob: 7eaa15360f5ff97507407169638ebae731853274 [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 Palsulef948d812023-05-25 16:53:01 -0500188 BMCWEB_LOG_ERROR << "Failed to find the handler";
189 conn.close("Internal error");
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 {
196 BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd"
197 << " error: " << 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
202 BMCWEB_LOG_DEBUG << "Console web socket path: " << conn.req.target()
203 << " Console unix FD: " << unixfd << " duped FD: " << fd;
204
Ninad Palsulef948d812023-05-25 16:53:01 -0500205 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500206 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500207 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500208 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500209 }
210}
211
212// Query consoles from DBUS and find the matching to the
213// rules string.
214inline void onOpen(crow::websocket::Connection& conn)
215{
216 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
217
Ninad Palsulef948d812023-05-25 16:53:01 -0500218 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500219 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500220 conn.close("Max sessions are already connected");
221 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500222 }
223
Ninad Palsulef948d812023-05-25 16:53:01 -0500224 std::shared_ptr<ConsoleHandler> handler =
225 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
226 getConsoleHandlerMap().emplace(&conn, handler);
227
228 conn.deferRead();
229
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500230 // The console id 'default' is used for the console0
231 // We need to change it when we provide full multi-console support.
232 const std::string consolePath = "/xyz/openbmc_project/console/default";
233 const std::string consoleService = "xyz.openbmc_project.Console.default";
234
235 BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath
236 << " service = " << consoleService
237 << " Request target = " << conn.req.target();
238
239 // Call Connect() method to get the unix FD
240 crow::connections::systemBus->async_method_call(
241 [&conn](const boost::system::error_code& ec,
242 const sdbusplus::message::unix_fd& unixfd) {
243 connectConsoleSocket(conn, ec, unixfd);
244 },
245 consoleService, consolePath, "xyz.openbmc_project.Console.Access",
246 "Connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700247}
248
Ninad Palsulef948d812023-05-25 16:53:01 -0500249inline void onMessage(crow::websocket::Connection& conn,
250 const std::string& data, bool /*isBinary*/)
251{
252 auto handler = getConsoleHandlerMap().find(&conn);
253 if (handler == getConsoleHandlerMap().end())
254 {
255 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn;
256 return;
257 }
258 handler->second->inputBuffer += data;
259 handler->second->doWrite();
260}
261
Ed Tanous23a21a12020-07-25 04:45:05 +0000262inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700263{
264 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500265 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700266 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500267 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500268 .onclose(onClose)
269 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700270}
271} // namespace obmc_console
272} // namespace crow