xref: /openbmc/bmcweb/include/obmc_console.hpp (revision d98a2f9388b9cab1100d30de401da43c32c98ef4)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
32daf6725SEd Tanous #pragma once
43ccb3adbSEd Tanous #include "app.hpp"
5d7857201SEd Tanous #include "dbus_singleton.hpp"
6d7857201SEd Tanous #include "dbus_utility.hpp"
7*d98a2f93SEd Tanous #include "io_context_singleton.hpp"
8d7857201SEd Tanous #include "logging.hpp"
9faf100f9SEd Tanous #include "websocket.hpp"
103ccb3adbSEd Tanous 
11d7857201SEd Tanous #include <sys/types.h>
12d7857201SEd Tanous #include <unistd.h>
132daf6725SEd Tanous 
14d7857201SEd Tanous #include <boost/asio/buffer.hpp>
15d7857201SEd Tanous #include <boost/asio/error.hpp>
16d7857201SEd Tanous #include <boost/asio/io_context.hpp>
17d4d77e39SEd Tanous #include <boost/asio/local/stream_protocol.hpp>
18d7857201SEd Tanous #include <boost/beast/core/error.hpp>
19f948d810SNinad Palsule #include <boost/container/flat_map.hpp>
208b901d78SGunnar Mills #include <boost/system/error_code.hpp>
21d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp>
228b901d78SGunnar Mills 
238b901d78SGunnar Mills #include <array>
24d7857201SEd Tanous #include <cstddef>
25d7857201SEd Tanous #include <functional>
268b901d78SGunnar Mills #include <memory>
278b901d78SGunnar Mills #include <string>
288b901d78SGunnar Mills #include <string_view>
29d7857201SEd Tanous #include <utility>
30d7857201SEd Tanous #include <vector>
312daf6725SEd Tanous 
322daf6725SEd Tanous namespace crow
332daf6725SEd Tanous {
342daf6725SEd Tanous namespace obmc_console
352daf6725SEd Tanous {
362daf6725SEd Tanous 
37f948d810SNinad Palsule // Update this value each time we add new console route.
38f948d810SNinad Palsule static constexpr const uint maxSessions = 32;
392daf6725SEd Tanous 
40f948d810SNinad Palsule class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
41f948d810SNinad Palsule {
42f948d810SNinad Palsule   public:
ConsoleHandler(boost::asio::io_context & ioc,crow::websocket::Connection & connIn)43f948d810SNinad Palsule     ConsoleHandler(boost::asio::io_context& ioc,
44f948d810SNinad Palsule                    crow::websocket::Connection& connIn) :
45bd79bce8SPatrick Williams         hostSocket(ioc), conn(connIn)
46f948d810SNinad Palsule     {}
472daf6725SEd Tanous 
48f948d810SNinad Palsule     ~ConsoleHandler() = default;
492daf6725SEd Tanous 
50f948d810SNinad Palsule     ConsoleHandler(const ConsoleHandler&) = delete;
51f948d810SNinad Palsule     ConsoleHandler(ConsoleHandler&&) = delete;
52f948d810SNinad Palsule     ConsoleHandler& operator=(const ConsoleHandler&) = delete;
53f948d810SNinad Palsule     ConsoleHandler& operator=(ConsoleHandler&&) = delete;
542daf6725SEd Tanous 
doWrite()55f948d810SNinad Palsule     void doWrite()
562daf6725SEd Tanous     {
572daf6725SEd Tanous         if (doingWrite)
582daf6725SEd Tanous         {
5962598e31SEd Tanous             BMCWEB_LOG_DEBUG("Already writing.  Bailing out");
602daf6725SEd Tanous             return;
612daf6725SEd Tanous         }
622daf6725SEd Tanous 
632daf6725SEd Tanous         if (inputBuffer.empty())
642daf6725SEd Tanous         {
6562598e31SEd Tanous             BMCWEB_LOG_DEBUG("Outbuffer empty.  Bailing out");
662daf6725SEd Tanous             return;
672daf6725SEd Tanous         }
682daf6725SEd Tanous 
69f948d810SNinad Palsule         doingWrite = true;
70f948d810SNinad Palsule         hostSocket.async_write_some(
71f948d810SNinad Palsule             boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
72f948d810SNinad Palsule             [weak(weak_from_this())](const boost::beast::error_code& ec,
73f948d810SNinad Palsule                                      std::size_t bytesWritten) {
74f948d810SNinad Palsule                 std::shared_ptr<ConsoleHandler> self = weak.lock();
75f948d810SNinad Palsule                 if (self == nullptr)
765238bd32SAppaRao Puli                 {
775238bd32SAppaRao Puli                     return;
785238bd32SAppaRao Puli                 }
795238bd32SAppaRao Puli 
80f948d810SNinad Palsule                 self->doingWrite = false;
81f948d810SNinad Palsule                 self->inputBuffer.erase(0, bytesWritten);
822daf6725SEd Tanous 
832daf6725SEd Tanous                 if (ec == boost::asio::error::eof)
842daf6725SEd Tanous                 {
85f948d810SNinad Palsule                     self->conn.close("Error in reading to host port");
862daf6725SEd Tanous                     return;
872daf6725SEd Tanous                 }
882daf6725SEd Tanous                 if (ec)
892daf6725SEd Tanous                 {
90bd79bce8SPatrick Williams                     BMCWEB_LOG_ERROR("Error in host serial write {}",
91bd79bce8SPatrick Williams                                      ec.message());
922daf6725SEd Tanous                     return;
932daf6725SEd Tanous                 }
94f948d810SNinad Palsule                 self->doWrite();
952daf6725SEd Tanous             });
962daf6725SEd Tanous     }
972daf6725SEd Tanous 
afterSendEx(const std::weak_ptr<ConsoleHandler> & weak)98099793f4SEd Tanous     static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
99099793f4SEd Tanous     {
100099793f4SEd Tanous         std::shared_ptr<ConsoleHandler> self = weak.lock();
101099793f4SEd Tanous         if (self == nullptr)
102099793f4SEd Tanous         {
103099793f4SEd Tanous             return;
104099793f4SEd Tanous         }
105099793f4SEd Tanous         self->doRead();
106099793f4SEd Tanous     }
107099793f4SEd Tanous 
doRead()108f948d810SNinad Palsule     void doRead()
1092daf6725SEd Tanous     {
11062598e31SEd Tanous         BMCWEB_LOG_DEBUG("Reading from socket");
111f948d810SNinad Palsule         hostSocket.async_read_some(
112099793f4SEd Tanous             boost::asio::buffer(outputBuffer),
113f948d810SNinad Palsule             [this, weakSelf(weak_from_this())](
114f948d810SNinad Palsule                 const boost::system::error_code& ec, std::size_t bytesRead) {
11562598e31SEd Tanous                 BMCWEB_LOG_DEBUG("read done.  Read {} bytes", bytesRead);
116f948d810SNinad Palsule                 std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
117f948d810SNinad Palsule                 if (self == nullptr)
118f948d810SNinad Palsule                 {
119f948d810SNinad Palsule                     return;
120f948d810SNinad Palsule                 }
1212daf6725SEd Tanous                 if (ec)
1222daf6725SEd Tanous                 {
12362598e31SEd Tanous                     BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
12462598e31SEd Tanous                                      ec.message());
125f948d810SNinad Palsule                     conn.close("Error connecting to host port");
1262daf6725SEd Tanous                     return;
1272daf6725SEd Tanous                 }
128099793f4SEd Tanous                 std::string_view payload(outputBuffer.data(), bytesRead);
129bd79bce8SPatrick Williams                 self->conn.sendEx(
130bd79bce8SPatrick Williams                     crow::websocket::MessageType::Binary, payload,
131099793f4SEd Tanous                     std::bind_front(afterSendEx, weak_from_this()));
1322daf6725SEd Tanous             });
1332daf6725SEd Tanous     }
1342daf6725SEd Tanous 
connect(int fd)135f948d810SNinad Palsule     bool connect(int fd)
1362daf6725SEd Tanous     {
137f948d810SNinad Palsule         boost::system::error_code ec;
138f948d810SNinad Palsule         boost::asio::local::stream_protocol proto;
139a8d4b8e6SNinad Palsule 
140f948d810SNinad Palsule         hostSocket.assign(proto, fd, ec);
141f948d810SNinad Palsule 
142f948d810SNinad Palsule         if (ec)
143a8d4b8e6SNinad Palsule         {
14462598e31SEd Tanous             BMCWEB_LOG_ERROR(
14562598e31SEd Tanous                 "Failed to assign the DBUS socket Socket assign error: {}",
14662598e31SEd Tanous                 ec.message());
147f948d810SNinad Palsule             return false;
148a8d4b8e6SNinad Palsule         }
149a8d4b8e6SNinad Palsule 
150f948d810SNinad Palsule         conn.resumeRead();
151f948d810SNinad Palsule         doWrite();
152f948d810SNinad Palsule         doRead();
153f948d810SNinad Palsule         return true;
154a8d4b8e6SNinad Palsule     }
155f948d810SNinad Palsule 
156f948d810SNinad Palsule     boost::asio::local::stream_protocol::socket hostSocket;
157f948d810SNinad Palsule 
158099793f4SEd Tanous     std::array<char, 4096> outputBuffer{};
159f948d810SNinad Palsule 
160f948d810SNinad Palsule     std::string inputBuffer;
161f948d810SNinad Palsule     bool doingWrite = false;
162f948d810SNinad Palsule     crow::websocket::Connection& conn;
163f948d810SNinad Palsule };
164f948d810SNinad Palsule 
165f948d810SNinad Palsule using ObmcConsoleMap = boost::container::flat_map<
166f948d810SNinad Palsule     crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
167f948d810SNinad Palsule     std::vector<std::pair<crow::websocket::Connection*,
168f948d810SNinad Palsule                           std::shared_ptr<ConsoleHandler>>>>;
169f948d810SNinad Palsule 
getConsoleHandlerMap()170f948d810SNinad Palsule inline ObmcConsoleMap& getConsoleHandlerMap()
171f948d810SNinad Palsule {
172f948d810SNinad Palsule     static ObmcConsoleMap map;
173f948d810SNinad Palsule     return map;
174f948d810SNinad Palsule }
175f948d810SNinad Palsule 
176f948d810SNinad Palsule // Remove connection from the connection map and if connection map is empty
177f948d810SNinad Palsule // then remove the handler from handlers map.
onClose(crow::websocket::Connection & conn,const std::string & err)178f948d810SNinad Palsule inline void onClose(crow::websocket::Connection& conn, const std::string& err)
179f948d810SNinad Palsule {
18062598e31SEd Tanous     BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
181f948d810SNinad Palsule 
182f948d810SNinad Palsule     auto iter = getConsoleHandlerMap().find(&conn);
183f948d810SNinad Palsule     if (iter == getConsoleHandlerMap().end())
184f948d810SNinad Palsule     {
18562598e31SEd Tanous         BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
186f948d810SNinad Palsule         return;
187f948d810SNinad Palsule     }
18862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
189f948d810SNinad Palsule 
190f948d810SNinad Palsule     // Removed last connection so remove the path
191f948d810SNinad Palsule     getConsoleHandlerMap().erase(iter);
192a8d4b8e6SNinad Palsule }
193a8d4b8e6SNinad Palsule 
connectConsoleSocket(crow::websocket::Connection & conn,const boost::system::error_code & ec,const sdbusplus::message::unix_fd & unixfd)194a8d4b8e6SNinad Palsule inline void connectConsoleSocket(crow::websocket::Connection& conn,
195a8d4b8e6SNinad Palsule                                  const boost::system::error_code& ec,
196a8d4b8e6SNinad Palsule                                  const sdbusplus::message::unix_fd& unixfd)
197a8d4b8e6SNinad Palsule {
1982daf6725SEd Tanous     if (ec)
1992daf6725SEd Tanous     {
20062598e31SEd Tanous         BMCWEB_LOG_ERROR(
20162598e31SEd Tanous             "Failed to call console Connect() method DBUS error: {}",
20262598e31SEd Tanous             ec.message());
203f948d810SNinad Palsule         conn.close("Failed to connect");
2042daf6725SEd Tanous         return;
2052daf6725SEd Tanous     }
2062daf6725SEd Tanous 
207f948d810SNinad Palsule     // Look up the handler
208f948d810SNinad Palsule     auto iter = getConsoleHandlerMap().find(&conn);
209f948d810SNinad Palsule     if (iter == getConsoleHandlerMap().end())
210a8d4b8e6SNinad Palsule     {
21162598e31SEd Tanous         BMCWEB_LOG_ERROR("Connection was already closed");
212a8d4b8e6SNinad Palsule         return;
213a8d4b8e6SNinad Palsule     }
214a8d4b8e6SNinad Palsule 
215f948d810SNinad Palsule     int fd = dup(unixfd);
216a8d4b8e6SNinad Palsule     if (fd == -1)
217a8d4b8e6SNinad Palsule     {
2187da633f0SEd Tanous         BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error");
219f948d810SNinad Palsule         conn.close("Internal error");
220a8d4b8e6SNinad Palsule         return;
221a8d4b8e6SNinad Palsule     }
222a8d4b8e6SNinad Palsule 
22362598e31SEd Tanous     BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
224a8d4b8e6SNinad Palsule 
225f948d810SNinad Palsule     if (!iter->second->connect(fd))
226a8d4b8e6SNinad Palsule     {
227a8d4b8e6SNinad Palsule         close(fd);
228f948d810SNinad Palsule         conn.close("Internal Error");
229a8d4b8e6SNinad Palsule     }
230a8d4b8e6SNinad Palsule }
231a8d4b8e6SNinad Palsule 
processConsoleObject(crow::websocket::Connection & conn,const std::string & consoleObjPath,const boost::system::error_code & ec,const::dbus::utility::MapperGetObject & objInfo)232bd79bce8SPatrick Williams inline void processConsoleObject(
233bd79bce8SPatrick Williams     crow::websocket::Connection& conn, const std::string& consoleObjPath,
234052bcbf4SNinad Palsule     const boost::system::error_code& ec,
235052bcbf4SNinad Palsule     const ::dbus::utility::MapperGetObject& objInfo)
236052bcbf4SNinad Palsule {
237052bcbf4SNinad Palsule     // Look up the handler
238052bcbf4SNinad Palsule     auto iter = getConsoleHandlerMap().find(&conn);
239052bcbf4SNinad Palsule     if (iter == getConsoleHandlerMap().end())
240052bcbf4SNinad Palsule     {
24162598e31SEd Tanous         BMCWEB_LOG_ERROR("Connection was already closed");
242052bcbf4SNinad Palsule         return;
243052bcbf4SNinad Palsule     }
244052bcbf4SNinad Palsule 
245052bcbf4SNinad Palsule     if (ec)
246052bcbf4SNinad Palsule     {
24762598e31SEd Tanous         BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
24862598e31SEd Tanous                            ec.message());
249052bcbf4SNinad Palsule         conn.close("getDbusObject() for consoles failed.");
250052bcbf4SNinad Palsule         return;
251052bcbf4SNinad Palsule     }
252052bcbf4SNinad Palsule 
253052bcbf4SNinad Palsule     const auto valueIface = objInfo.begin();
254052bcbf4SNinad Palsule     if (valueIface == objInfo.end())
255052bcbf4SNinad Palsule     {
25662598e31SEd Tanous         BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
25762598e31SEd Tanous                            objInfo.size());
258052bcbf4SNinad Palsule         conn.close("getDbusObject() returned unexpected size");
259052bcbf4SNinad Palsule         return;
260052bcbf4SNinad Palsule     }
261052bcbf4SNinad Palsule 
262052bcbf4SNinad Palsule     const std::string& consoleService = valueIface->first;
26362598e31SEd Tanous     BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
26462598e31SEd Tanous                      consoleObjPath);
265052bcbf4SNinad Palsule     // Call Connect() method to get the unix FD
266052bcbf4SNinad Palsule     crow::connections::systemBus->async_method_call(
267052bcbf4SNinad Palsule         [&conn](const boost::system::error_code& ec1,
268052bcbf4SNinad Palsule                 const sdbusplus::message::unix_fd& unixfd) {
269052bcbf4SNinad Palsule             connectConsoleSocket(conn, ec1, unixfd);
270052bcbf4SNinad Palsule         },
271052bcbf4SNinad Palsule         consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
272052bcbf4SNinad Palsule         "Connect");
273052bcbf4SNinad Palsule }
274052bcbf4SNinad Palsule 
275a8d4b8e6SNinad Palsule // Query consoles from DBUS and find the matching to the
276a8d4b8e6SNinad Palsule // rules string.
onOpen(crow::websocket::Connection & conn)277a8d4b8e6SNinad Palsule inline void onOpen(crow::websocket::Connection& conn)
278a8d4b8e6SNinad Palsule {
279052bcbf4SNinad Palsule     std::string consoleLeaf;
280052bcbf4SNinad Palsule 
28162598e31SEd Tanous     BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
282a8d4b8e6SNinad Palsule 
283f948d810SNinad Palsule     if (getConsoleHandlerMap().size() >= maxSessions)
284a8d4b8e6SNinad Palsule     {
285f948d810SNinad Palsule         conn.close("Max sessions are already connected");
286f948d810SNinad Palsule         return;
287a8d4b8e6SNinad Palsule     }
288a8d4b8e6SNinad Palsule 
289f948d810SNinad Palsule     std::shared_ptr<ConsoleHandler> handler =
290*d98a2f93SEd Tanous         std::make_shared<ConsoleHandler>(getIoContext(), conn);
291f948d810SNinad Palsule     getConsoleHandlerMap().emplace(&conn, handler);
292f948d810SNinad Palsule 
293f948d810SNinad Palsule     conn.deferRead();
294f948d810SNinad Palsule 
295052bcbf4SNinad Palsule     // Keep old path for backward compatibility
296052bcbf4SNinad Palsule     if (conn.url().path() == "/console0")
297052bcbf4SNinad Palsule     {
298052bcbf4SNinad Palsule         consoleLeaf = "default";
299052bcbf4SNinad Palsule     }
300052bcbf4SNinad Palsule     else
301052bcbf4SNinad Palsule     {
302052bcbf4SNinad Palsule         // Get the console id from console router path and prepare the console
303052bcbf4SNinad Palsule         // object path and console service.
304052bcbf4SNinad Palsule         consoleLeaf = conn.url().segments().back();
305052bcbf4SNinad Palsule     }
306052bcbf4SNinad Palsule     std::string consolePath =
307052bcbf4SNinad Palsule         sdbusplus::message::object_path("/xyz/openbmc_project/console") /
308052bcbf4SNinad Palsule         consoleLeaf;
309a8d4b8e6SNinad Palsule 
31062598e31SEd Tanous     BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
31162598e31SEd Tanous                      consolePath, conn.url().path());
312a8d4b8e6SNinad Palsule 
313052bcbf4SNinad Palsule     // mapper call lambda
314052bcbf4SNinad Palsule     constexpr std::array<std::string_view, 1> interfaces = {
315052bcbf4SNinad Palsule         "xyz.openbmc_project.Console.Access"};
316052bcbf4SNinad Palsule 
317052bcbf4SNinad Palsule     dbus::utility::getDbusObject(
318052bcbf4SNinad Palsule         consolePath, interfaces,
319052bcbf4SNinad Palsule         [&conn, consolePath](const boost::system::error_code& ec,
320052bcbf4SNinad Palsule                              const ::dbus::utility::MapperGetObject& objInfo) {
321052bcbf4SNinad Palsule             processConsoleObject(conn, consolePath, ec, objInfo);
322052bcbf4SNinad Palsule         });
323a8d4b8e6SNinad Palsule }
3242daf6725SEd Tanous 
onMessage(crow::websocket::Connection & conn,const std::string & data,bool)325f948d810SNinad Palsule inline void onMessage(crow::websocket::Connection& conn,
326f948d810SNinad Palsule                       const std::string& data, bool /*isBinary*/)
327f948d810SNinad Palsule {
328f948d810SNinad Palsule     auto handler = getConsoleHandlerMap().find(&conn);
329f948d810SNinad Palsule     if (handler == getConsoleHandlerMap().end())
330f948d810SNinad Palsule     {
33162598e31SEd Tanous         BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
332f948d810SNinad Palsule         return;
333f948d810SNinad Palsule     }
334f948d810SNinad Palsule     handler->second->inputBuffer += data;
335f948d810SNinad Palsule     handler->second->doWrite();
336f948d810SNinad Palsule }
337f948d810SNinad Palsule 
requestRoutes(App & app)33823a21a1cSEd Tanous inline void requestRoutes(App& app)
3392daf6725SEd Tanous {
3402daf6725SEd Tanous     BMCWEB_ROUTE(app, "/console0")
3413e72c202SNinad Palsule         .privileges({{"OpenBMCHostConsole"}})
3422daf6725SEd Tanous         .websocket()
343a8d4b8e6SNinad Palsule         .onopen(onOpen)
344f948d810SNinad Palsule         .onclose(onClose)
345f948d810SNinad Palsule         .onmessage(onMessage);
346052bcbf4SNinad Palsule 
347052bcbf4SNinad Palsule     BMCWEB_ROUTE(app, "/console/<str>")
348052bcbf4SNinad Palsule         .privileges({{"OpenBMCHostConsole"}})
349052bcbf4SNinad Palsule         .websocket()
350052bcbf4SNinad Palsule         .onopen(onOpen)
351052bcbf4SNinad Palsule         .onclose(onClose)
352052bcbf4SNinad Palsule         .onmessage(onMessage);
3532daf6725SEd Tanous }
3542daf6725SEd Tanous } // namespace obmc_console
3552daf6725SEd Tanous } // namespace crow
356