blob: e9c2b153e71e1bad4cf0e28bb63a0415a42b6a2a [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous2daf6722018-08-23 11:27:23 -07003#pragma once
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08004#include "app.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08005#include "dbus_singleton.hpp"
6#include "dbus_utility.hpp"
7#include "logging.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07008#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08009
Ed Tanousd7857202025-01-28 15:32:26 -080010#include <sys/types.h>
11#include <unistd.h>
Ed Tanous2daf6722018-08-23 11:27:23 -070012
Ed Tanousd7857202025-01-28 15:32:26 -080013#include <boost/asio/buffer.hpp>
14#include <boost/asio/error.hpp>
15#include <boost/asio/io_context.hpp>
Ed Tanousd4d77e32020-08-18 00:07:28 -070016#include <boost/asio/local/stream_protocol.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080017#include <boost/beast/core/error.hpp>
Ninad Palsulef948d812023-05-25 16:53:01 -050018#include <boost/container/flat_map.hpp>
Gunnar Mills8b901d72024-05-03 16:58:32 -050019#include <boost/system/error_code.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <sdbusplus/message/native_types.hpp>
Gunnar Mills8b901d72024-05-03 16:58:32 -050021
22#include <array>
Ed Tanousd7857202025-01-28 15:32:26 -080023#include <cstddef>
24#include <functional>
Gunnar Mills8b901d72024-05-03 16:58:32 -050025#include <memory>
26#include <string>
27#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080028#include <utility>
29#include <vector>
Ed Tanous2daf6722018-08-23 11:27:23 -070030
31namespace crow
32{
33namespace obmc_console
34{
35
Ninad Palsulef948d812023-05-25 16:53:01 -050036// Update this value each time we add new console route.
37static constexpr const uint maxSessions = 32;
Ed Tanous2daf6722018-08-23 11:27:23 -070038
Ninad Palsulef948d812023-05-25 16:53:01 -050039class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
Ed Tanous2daf6722018-08-23 11:27:23 -070040{
Ninad Palsulef948d812023-05-25 16:53:01 -050041 public:
42 ConsoleHandler(boost::asio::io_context& ioc,
43 crow::websocket::Connection& connIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040044 hostSocket(ioc), conn(connIn)
Ninad Palsulef948d812023-05-25 16:53:01 -050045 {}
46
47 ~ConsoleHandler() = default;
48
49 ConsoleHandler(const ConsoleHandler&) = delete;
50 ConsoleHandler(ConsoleHandler&&) = delete;
51 ConsoleHandler& operator=(const ConsoleHandler&) = delete;
52 ConsoleHandler& operator=(ConsoleHandler&&) = delete;
53
54 void doWrite()
Ed Tanous2daf6722018-08-23 11:27:23 -070055 {
Ninad Palsulef948d812023-05-25 16:53:01 -050056 if (doingWrite)
Ed Tanous002d39b2022-05-31 08:59:27 -070057 {
Ed Tanous62598e32023-07-17 17:06:25 -070058 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Ed Tanous002d39b2022-05-31 08:59:27 -070059 return;
60 }
Ninad Palsulef948d812023-05-25 16:53:01 -050061
62 if (inputBuffer.empty())
63 {
Ed Tanous62598e32023-07-17 17:06:25 -070064 BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out");
Ninad Palsulef948d812023-05-25 16:53:01 -050065 return;
66 }
67
68 doingWrite = true;
69 hostSocket.async_write_some(
70 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
71 [weak(weak_from_this())](const boost::beast::error_code& ec,
72 std::size_t bytesWritten) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040073 std::shared_ptr<ConsoleHandler> self = weak.lock();
74 if (self == nullptr)
75 {
76 return;
77 }
Ninad Palsulef948d812023-05-25 16:53:01 -050078
Patrick Williamsbd79bce2024-08-16 15:22:20 -040079 self->doingWrite = false;
80 self->inputBuffer.erase(0, bytesWritten);
Ninad Palsulef948d812023-05-25 16:53:01 -050081
Patrick Williamsbd79bce2024-08-16 15:22:20 -040082 if (ec == boost::asio::error::eof)
83 {
84 self->conn.close("Error in reading to host port");
85 return;
86 }
87 if (ec)
88 {
89 BMCWEB_LOG_ERROR("Error in host serial write {}",
90 ec.message());
91 return;
92 }
93 self->doWrite();
94 });
Ninad Palsulef948d812023-05-25 16:53:01 -050095 }
96
Ed Tanous099793f2023-05-31 08:54:26 -070097 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
98 {
99 std::shared_ptr<ConsoleHandler> self = weak.lock();
100 if (self == nullptr)
101 {
102 return;
103 }
104 self->doRead();
105 }
106
Ninad Palsulef948d812023-05-25 16:53:01 -0500107 void doRead()
108 {
Ed Tanous62598e32023-07-17 17:06:25 -0700109 BMCWEB_LOG_DEBUG("Reading from socket");
Ninad Palsulef948d812023-05-25 16:53:01 -0500110 hostSocket.async_read_some(
Ed Tanous099793f2023-05-31 08:54:26 -0700111 boost::asio::buffer(outputBuffer),
Ninad Palsulef948d812023-05-25 16:53:01 -0500112 [this, weakSelf(weak_from_this())](
113 const boost::system::error_code& ec, std::size_t bytesRead) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400114 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead);
115 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
116 if (self == nullptr)
117 {
118 return;
119 }
120 if (ec)
121 {
122 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
123 ec.message());
124 conn.close("Error connecting to host port");
125 return;
126 }
127 std::string_view payload(outputBuffer.data(), bytesRead);
128 self->conn.sendEx(
129 crow::websocket::MessageType::Binary, payload,
130 std::bind_front(afterSendEx, weak_from_this()));
131 });
Ninad Palsulef948d812023-05-25 16:53:01 -0500132 }
133
134 bool connect(int fd)
135 {
136 boost::system::error_code ec;
137 boost::asio::local::stream_protocol proto;
138
139 hostSocket.assign(proto, fd, ec);
140
Ed Tanous002d39b2022-05-31 08:59:27 -0700141 if (ec)
142 {
Ed Tanous62598e32023-07-17 17:06:25 -0700143 BMCWEB_LOG_ERROR(
144 "Failed to assign the DBUS socket Socket assign error: {}",
145 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500146 return false;
Ed Tanous002d39b2022-05-31 08:59:27 -0700147 }
Ninad Palsulef948d812023-05-25 16:53:01 -0500148
149 conn.resumeRead();
Ed Tanous002d39b2022-05-31 08:59:27 -0700150 doWrite();
Ninad Palsulef948d812023-05-25 16:53:01 -0500151 doRead();
152 return true;
153 }
154
155 boost::asio::local::stream_protocol::socket hostSocket;
156
Ed Tanous099793f2023-05-31 08:54:26 -0700157 std::array<char, 4096> outputBuffer{};
Ninad Palsulef948d812023-05-25 16:53:01 -0500158
159 std::string inputBuffer;
160 bool doingWrite = false;
161 crow::websocket::Connection& conn;
162};
163
164using ObmcConsoleMap = boost::container::flat_map<
165 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
166 std::vector<std::pair<crow::websocket::Connection*,
167 std::shared_ptr<ConsoleHandler>>>>;
168
169inline ObmcConsoleMap& getConsoleHandlerMap()
170{
171 static ObmcConsoleMap map;
172 return map;
Ed Tanous2daf6722018-08-23 11:27:23 -0700173}
174
Ninad Palsulef948d812023-05-25 16:53:01 -0500175// Remove connection from the connection map and if connection map is empty
176// then remove the handler from handlers map.
177inline void onClose(crow::websocket::Connection& conn, const std::string& err)
Ed Tanous2daf6722018-08-23 11:27:23 -0700178{
Ed Tanous62598e32023-07-17 17:06:25 -0700179 BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
Ninad Palsulef948d812023-05-25 16:53:01 -0500180
181 auto iter = getConsoleHandlerMap().find(&conn);
182 if (iter == getConsoleHandlerMap().end())
AppaRao Puli5238bd32020-11-17 11:03:11 +0530183 {
Ed Tanous62598e32023-07-17 17:06:25 -0700184 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530185 return;
186 }
Ed Tanous62598e32023-07-17 17:06:25 -0700187 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
AppaRao Puli5238bd32020-11-17 11:03:11 +0530188
Ninad Palsulef948d812023-05-25 16:53:01 -0500189 // Removed last connection so remove the path
190 getConsoleHandlerMap().erase(iter);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500191}
192
193inline void connectConsoleSocket(crow::websocket::Connection& conn,
194 const boost::system::error_code& ec,
195 const sdbusplus::message::unix_fd& unixfd)
196{
Ed Tanous2daf6722018-08-23 11:27:23 -0700197 if (ec)
198 {
Ed Tanous62598e32023-07-17 17:06:25 -0700199 BMCWEB_LOG_ERROR(
200 "Failed to call console Connect() method DBUS error: {}",
201 ec.message());
Ninad Palsulef948d812023-05-25 16:53:01 -0500202 conn.close("Failed to connect");
Ed Tanous2daf6722018-08-23 11:27:23 -0700203 return;
204 }
205
Ninad Palsulef948d812023-05-25 16:53:01 -0500206 // Look up the handler
207 auto iter = getConsoleHandlerMap().find(&conn);
208 if (iter == getConsoleHandlerMap().end())
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500209 {
Ed Tanous62598e32023-07-17 17:06:25 -0700210 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500211 return;
212 }
213
Ninad Palsulef948d812023-05-25 16:53:01 -0500214 int fd = dup(unixfd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500215 if (fd == -1)
216 {
Ed Tanous7da633f2024-12-02 08:25:38 -0800217 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error");
Ninad Palsulef948d812023-05-25 16:53:01 -0500218 conn.close("Internal error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500219 return;
220 }
221
Ed Tanous62598e32023-07-17 17:06:25 -0700222 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500223
Ninad Palsulef948d812023-05-25 16:53:01 -0500224 if (!iter->second->connect(fd))
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500225 {
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500226 close(fd);
Ninad Palsulef948d812023-05-25 16:53:01 -0500227 conn.close("Internal Error");
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500228 }
229}
230
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400231inline void processConsoleObject(
232 crow::websocket::Connection& conn, const std::string& consoleObjPath,
233 const boost::system::error_code& ec,
234 const ::dbus::utility::MapperGetObject& objInfo)
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500235{
236 // Look up the handler
237 auto iter = getConsoleHandlerMap().find(&conn);
238 if (iter == getConsoleHandlerMap().end())
239 {
Ed Tanous62598e32023-07-17 17:06:25 -0700240 BMCWEB_LOG_ERROR("Connection was already closed");
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500241 return;
242 }
243
244 if (ec)
245 {
Ed Tanous62598e32023-07-17 17:06:25 -0700246 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
247 ec.message());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500248 conn.close("getDbusObject() for consoles failed.");
249 return;
250 }
251
252 const auto valueIface = objInfo.begin();
253 if (valueIface == objInfo.end())
254 {
Ed Tanous62598e32023-07-17 17:06:25 -0700255 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
256 objInfo.size());
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500257 conn.close("getDbusObject() returned unexpected size");
258 return;
259 }
260
261 const std::string& consoleService = valueIface->first;
Ed Tanous62598e32023-07-17 17:06:25 -0700262 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
263 consoleObjPath);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500264 // Call Connect() method to get the unix FD
265 crow::connections::systemBus->async_method_call(
266 [&conn](const boost::system::error_code& ec1,
267 const sdbusplus::message::unix_fd& unixfd) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400268 connectConsoleSocket(conn, ec1, unixfd);
269 },
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500270 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
271 "Connect");
272}
273
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500274// Query consoles from DBUS and find the matching to the
275// rules string.
276inline void onOpen(crow::websocket::Connection& conn)
277{
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500278 std::string consoleLeaf;
279
Ed Tanous62598e32023-07-17 17:06:25 -0700280 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500281
Ninad Palsulef948d812023-05-25 16:53:01 -0500282 if (getConsoleHandlerMap().size() >= maxSessions)
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500283 {
Ninad Palsulef948d812023-05-25 16:53:01 -0500284 conn.close("Max sessions are already connected");
285 return;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500286 }
287
Ninad Palsulef948d812023-05-25 16:53:01 -0500288 std::shared_ptr<ConsoleHandler> handler =
289 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn);
290 getConsoleHandlerMap().emplace(&conn, handler);
291
292 conn.deferRead();
293
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500294 // Keep old path for backward compatibility
295 if (conn.url().path() == "/console0")
296 {
297 consoleLeaf = "default";
298 }
299 else
300 {
301 // Get the console id from console router path and prepare the console
302 // object path and console service.
303 consoleLeaf = conn.url().segments().back();
304 }
305 std::string consolePath =
306 sdbusplus::message::object_path("/xyz/openbmc_project/console") /
307 consoleLeaf;
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500308
Ed Tanous62598e32023-07-17 17:06:25 -0700309 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
310 consolePath, conn.url().path());
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500311
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500312 // mapper call lambda
313 constexpr std::array<std::string_view, 1> interfaces = {
314 "xyz.openbmc_project.Console.Access"};
315
316 dbus::utility::getDbusObject(
317 consolePath, interfaces,
318 [&conn, consolePath](const boost::system::error_code& ec,
319 const ::dbus::utility::MapperGetObject& objInfo) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400320 processConsoleObject(conn, consolePath, ec, objInfo);
321 });
Ed Tanous2daf6722018-08-23 11:27:23 -0700322}
323
Ninad Palsulef948d812023-05-25 16:53:01 -0500324inline void onMessage(crow::websocket::Connection& conn,
325 const std::string& data, bool /*isBinary*/)
326{
327 auto handler = getConsoleHandlerMap().find(&conn);
328 if (handler == getConsoleHandlerMap().end())
329 {
Ed Tanous62598e32023-07-17 17:06:25 -0700330 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
Ninad Palsulef948d812023-05-25 16:53:01 -0500331 return;
332 }
333 handler->second->inputBuffer += data;
334 handler->second->doWrite();
335}
336
Ed Tanous23a21a12020-07-25 04:45:05 +0000337inline void requestRoutes(App& app)
Ed Tanous2daf6722018-08-23 11:27:23 -0700338{
339 BMCWEB_ROUTE(app, "/console0")
Ninad Palsule3e72c202023-03-27 17:19:55 -0500340 .privileges({{"OpenBMCHostConsole"}})
Ed Tanous2daf6722018-08-23 11:27:23 -0700341 .websocket()
Ninad Palsulea8d4b8e2023-04-10 16:40:00 -0500342 .onopen(onOpen)
Ninad Palsulef948d812023-05-25 16:53:01 -0500343 .onclose(onClose)
344 .onmessage(onMessage);
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500345
346 BMCWEB_ROUTE(app, "/console/<str>")
347 .privileges({{"OpenBMCHostConsole"}})
348 .websocket()
349 .onopen(onOpen)
350 .onclose(onClose)
351 .onmessage(onMessage);
Ed Tanous2daf6722018-08-23 11:27:23 -0700352}
353} // namespace obmc_console
354} // namespace crow