blob: 70253cf8250e73a0d5aae8277ac45032a8c2c9eb [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
78 void doRead()
79 {
80 std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
81
82 BMCWEB_LOG_DEBUG << "Reading from socket";
83 hostSocket.async_read_some(
84 outputBuffer.prepare(bytes),
85 [this, weakSelf(weak_from_this())](
86 const boost::system::error_code& ec, std::size_t bytesRead) {
87 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes";
88 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
89 if (self == nullptr)
90 {
91 return;
92 }
93 if (ec)
94 {
95 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: "
96 << ec.message();
97 conn.close("Error connecting to host port");
98 return;
99 }
100 outputBuffer.commit(bytesRead);
101 std::string_view payload(
102 static_cast<const char*>(outputBuffer.data().data()),
103 bytesRead);
104 conn.sendBinary(payload);
105 outputBuffer.consume(bytesRead);
106 doRead();
107 });
108 }
109
110 bool connect(int fd)
111 {
112 boost::system::error_code ec;
113 boost::asio::local::stream_protocol proto;
114
115 hostSocket.assign(proto, fd, ec);
116
Ed Tanous002d39b2022-05-31 08:59:27 -0700117 if (ec)
118 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500119 BMCWEB_LOG_ERROR << "Failed to assign the DBUS socket"
120 << " Socket assign error: " << ec.message();
121 return false;
Ed Tanous002d39b2022-05-31 08:59:27 -0700122 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500123
124 conn.resumeRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700125 doWrite();
Ninad Palsulef948d812023-05-25 16:53:01 -0500126 doRead();
127 return true;
128 }
129
130 boost::asio::local::stream_protocol::socket hostSocket;
131
132 boost::beast::flat_static_buffer<4096> outputBuffer;
133
134 std::string inputBuffer;
135 bool doingWrite = false;
136 crow::websocket::Connection& conn;
137};
138
139using ObmcConsoleMap = boost::container::flat_map<
140 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
141 std::vector<std::pair<crow::websocket::Connection*,
142 std::shared_ptr<ConsoleHandler>>>>;
143
144inline ObmcConsoleMap& getConsoleHandlerMap()
145{
146 static ObmcConsoleMap map;
147 return map;
Ed Tanous2daf6722018-08-23 11:27:23 -0700148}
149
Ninad Palsulef948d812023-05-25 16:53:01 -0500150// Remove connection from the connection map and if connection map is empty
151// then remove the handler from handlers map.
152inline void onClose(crow::websocket::Connection& conn, const std::string& err)
Ed Tanous2daf6722018-08-23 11:27:23 -0700153{
Ninad Palsulef948d812023-05-25 16:53:01 -0500154 BMCWEB_LOG_INFO << "Closing websocket. Reason: " << err;
155
156 auto iter = getConsoleHandlerMap().find(&conn);
157 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530158 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500159 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn;
AppaRao Puli5238bd32020-11-17 11:03:11 +0530160 return;
161 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500162 BMCWEB_LOG_DEBUG << "Remove connection " << &conn << " from obmc console";
AppaRao Puli5238bd32020-11-17 11:03:11 +0530163
Ninad Palsulef948d812023-05-25 16:53:01 -0500164 // Removed last connection so remove the path
165 getConsoleHandlerMap().erase(iter);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500166}
167
168inline void connectConsoleSocket(crow::websocket::Connection& conn,
169 const boost::system::error_code& ec,
170 const sdbusplus::message::unix_fd& unixfd)
171{
Ed Tanous2daf6722018-08-23 11:27:23 -0700172 if (ec)
173 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500174 BMCWEB_LOG_ERROR << "Failed to call console Connect() method"
175 << " DBUS error: " << ec.message();
Ninad Palsulef948d812023-05-25 16:53:01 -0500176 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700177 return;
178 }
179
Ninad Palsulef948d812023-05-25 16:53:01 -0500180 // Look up the handler
181 auto iter = getConsoleHandlerMap().find(&conn);
182 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500183 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500184 BMCWEB_LOG_ERROR << "Failed to find the handler";
185 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500186 return;
187 }
188
Ninad Palsulef948d812023-05-25 16:53:01 -0500189 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500190 if (fd == -1)
191 {
192 BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd"
193 << " error: " << strerror(errno);
Ninad Palsulef948d812023-05-25 16:53:01 -0500194 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500195 return;
196 }
197
198 BMCWEB_LOG_DEBUG << "Console web socket path: " << conn.req.target()
199 << " Console unix FD: " << unixfd << " duped FD: " << fd;
200
Ninad Palsulef948d812023-05-25 16:53:01 -0500201 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500202 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500203 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500204 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500205 }
206}
207
208// Query consoles from DBUS and find the matching to the
209// rules string.
210inline void onOpen(crow::websocket::Connection& conn)
211{
212 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
213
Ninad Palsulef948d812023-05-25 16:53:01 -0500214 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500215 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500216 conn.close("Max sessions are already connected");
217 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500218 }
219
Ninad Palsulef948d812023-05-25 16:53:01 -0500220 std::shared_ptr<ConsoleHandler> handler =
221 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
222 getConsoleHandlerMap().emplace(&conn, handler);
223
224 conn.deferRead();
225
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500226 // The console id 'default' is used for the console0
227 // We need to change it when we provide full multi-console support.
228 const std::string consolePath = "/xyz/openbmc_project/console/default";
229 const std::string consoleService = "xyz.openbmc_project.Console.default";
230
231 BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath
232 << " service = " << consoleService
233 << " Request target = " << conn.req.target();
234
235 // Call Connect() method to get the unix FD
236 crow::connections::systemBus->async_method_call(
237 [&conn](const boost::system::error_code& ec,
238 const sdbusplus::message::unix_fd& unixfd) {
239 connectConsoleSocket(conn, ec, unixfd);
240 },
241 consoleService, consolePath, "xyz.openbmc_project.Console.Access",
242 "Connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700243}
244
Ninad Palsulef948d812023-05-25 16:53:01 -0500245inline void onMessage(crow::websocket::Connection& conn,
246 const std::string& data, bool /*isBinary*/)
247{
248 auto handler = getConsoleHandlerMap().find(&conn);
249 if (handler == getConsoleHandlerMap().end())
250 {
251 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn;
252 return;
253 }
254 handler->second->inputBuffer += data;
255 handler->second->doWrite();
256}
257
Ed Tanous23a21a12020-07-25 04:45:05 +0000258inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700259{
260 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500261 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700262 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500263 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500264 .onclose(onClose)
265 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700266}
267} // namespace obmc_console
268} // namespace crow