#pragma once #include "app.hpp" #include "async_resp.hpp" #include "websocket.hpp" #include #include #include #include #include #include #include #include namespace crow { namespace obmc_console { // Update this value each time we add new console route. static constexpr const uint maxSessions = 32; class ConsoleHandler : public std::enable_shared_from_this { public: ConsoleHandler(boost::asio::io_context& ioc, crow::websocket::Connection& connIn) : hostSocket(ioc), conn(connIn) {} ~ConsoleHandler() = default; ConsoleHandler(const ConsoleHandler&) = delete; ConsoleHandler(ConsoleHandler&&) = delete; ConsoleHandler& operator=(const ConsoleHandler&) = delete; ConsoleHandler& operator=(ConsoleHandler&&) = delete; void doWrite() { if (doingWrite) { BMCWEB_LOG_DEBUG("Already writing. Bailing out"); return; } if (inputBuffer.empty()) { BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out"); return; } doingWrite = true; hostSocket.async_write_some( boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), [weak(weak_from_this())](const boost::beast::error_code& ec, std::size_t bytesWritten) { std::shared_ptr self = weak.lock(); if (self == nullptr) { return; } self->doingWrite = false; self->inputBuffer.erase(0, bytesWritten); if (ec == boost::asio::error::eof) { self->conn.close("Error in reading to host port"); return; } if (ec) { BMCWEB_LOG_ERROR("Error in host serial write {}", ec.message()); return; } self->doWrite(); }); } static void afterSendEx(const std::weak_ptr& weak) { std::shared_ptr self = weak.lock(); if (self == nullptr) { return; } self->doRead(); } void doRead() { BMCWEB_LOG_DEBUG("Reading from socket"); hostSocket.async_read_some( boost::asio::buffer(outputBuffer), [this, weakSelf(weak_from_this())]( const boost::system::error_code& ec, std::size_t bytesRead) { BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead); std::shared_ptr self = weakSelf.lock(); if (self == nullptr) { return; } if (ec) { BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}", ec.message()); conn.close("Error connecting to host port"); return; } std::string_view payload(outputBuffer.data(), bytesRead); self->conn.sendEx(crow::websocket::MessageType::Binary, payload, std::bind_front(afterSendEx, weak_from_this())); }); } bool connect(int fd) { boost::system::error_code ec; boost::asio::local::stream_protocol proto; hostSocket.assign(proto, fd, ec); if (ec) { BMCWEB_LOG_ERROR( "Failed to assign the DBUS socket Socket assign error: {}", ec.message()); return false; } conn.resumeRead(); doWrite(); doRead(); return true; } boost::asio::local::stream_protocol::socket hostSocket; std::array outputBuffer{}; std::string inputBuffer; bool doingWrite = false; crow::websocket::Connection& conn; }; using ObmcConsoleMap = boost::container::flat_map< crow::websocket::Connection*, std::shared_ptr, std::less<>, std::vector>>>; inline ObmcConsoleMap& getConsoleHandlerMap() { static ObmcConsoleMap map; return map; } // Remove connection from the connection map and if connection map is empty // then remove the handler from handlers map. inline void onClose(crow::websocket::Connection& conn, const std::string& err) { BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err); auto iter = getConsoleHandlerMap().find(&conn); if (iter == getConsoleHandlerMap().end()) { BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn)); return; } BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn)); // Removed last connection so remove the path getConsoleHandlerMap().erase(iter); } inline void connectConsoleSocket(crow::websocket::Connection& conn, const boost::system::error_code& ec, const sdbusplus::message::unix_fd& unixfd) { if (ec) { BMCWEB_LOG_ERROR( "Failed to call console Connect() method DBUS error: {}", ec.message()); conn.close("Failed to connect"); return; } // Look up the handler auto iter = getConsoleHandlerMap().find(&conn); if (iter == getConsoleHandlerMap().end()) { BMCWEB_LOG_ERROR("Connection was already closed"); return; } int fd = dup(unixfd); if (fd == -1) { BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error: {}", strerror(errno)); conn.close("Internal error"); return; } BMCWEB_LOG_DEBUG("Console duped FD: {}", fd); if (!iter->second->connect(fd)) { close(fd); conn.close("Internal Error"); } } inline void processConsoleObject(crow::websocket::Connection& conn, const std::string& consoleObjPath, const boost::system::error_code& ec, const ::dbus::utility::MapperGetObject& objInfo) { // Look up the handler auto iter = getConsoleHandlerMap().find(&conn); if (iter == getConsoleHandlerMap().end()) { BMCWEB_LOG_ERROR("Connection was already closed"); return; } if (ec) { BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}", ec.message()); conn.close("getDbusObject() for consoles failed."); return; } const auto valueIface = objInfo.begin(); if (valueIface == objInfo.end()) { BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}", objInfo.size()); conn.close("getDbusObject() returned unexpected size"); return; } const std::string& consoleService = valueIface->first; BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService, consoleObjPath); // Call Connect() method to get the unix FD crow::connections::systemBus->async_method_call( [&conn](const boost::system::error_code& ec1, const sdbusplus::message::unix_fd& unixfd) { connectConsoleSocket(conn, ec1, unixfd); }, consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access", "Connect"); } // Query consoles from DBUS and find the matching to the // rules string. inline void onOpen(crow::websocket::Connection& conn) { std::string consoleLeaf; BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); if (getConsoleHandlerMap().size() >= maxSessions) { conn.close("Max sessions are already connected"); return; } std::shared_ptr handler = std::make_shared(conn.getIoContext(), conn); getConsoleHandlerMap().emplace(&conn, handler); conn.deferRead(); // Keep old path for backward compatibility if (conn.url().path() == "/console0") { consoleLeaf = "default"; } else { // Get the console id from console router path and prepare the console // object path and console service. consoleLeaf = conn.url().segments().back(); } std::string consolePath = sdbusplus::message::object_path("/xyz/openbmc_project/console") / consoleLeaf; BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}", consolePath, conn.url().path()); // mapper call lambda constexpr std::array interfaces = { "xyz.openbmc_project.Console.Access"}; dbus::utility::getDbusObject( consolePath, interfaces, [&conn, consolePath](const boost::system::error_code& ec, const ::dbus::utility::MapperGetObject& objInfo) { processConsoleObject(conn, consolePath, ec, objInfo); }); } inline void onMessage(crow::websocket::Connection& conn, const std::string& data, bool /*isBinary*/) { auto handler = getConsoleHandlerMap().find(&conn); if (handler == getConsoleHandlerMap().end()) { BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn)); return; } handler->second->inputBuffer += data; handler->second->doWrite(); } inline void requestRoutes(App& app) { BMCWEB_ROUTE(app, "/console0") .privileges({{"OpenBMCHostConsole"}}) .websocket() .onopen(onOpen) .onclose(onClose) .onmessage(onMessage); BMCWEB_ROUTE(app, "/console/") .privileges({{"OpenBMCHostConsole"}}) .websocket() .onopen(onOpen) .onclose(onClose) .onmessage(onMessage); } } // namespace obmc_console } // namespace crow