1 #pragma once 2 #include "app.hpp" 3 #include "async_resp.hpp" 4 #include "websocket.hpp" 5 6 #include <sys/socket.h> 7 8 #include <boost/asio/local/stream_protocol.hpp> 9 #include <boost/container/flat_map.hpp> 10 11 namespace crow 12 { 13 namespace obmc_console 14 { 15 16 // Update this value each time we add new console route. 17 static constexpr const uint maxSessions = 32; 18 19 class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler> 20 { 21 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() 36 { 37 if (doingWrite) 38 { 39 BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; 40 return; 41 } 42 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 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 88 void doRead() 89 { 90 BMCWEB_LOG_DEBUG << "Reading from socket"; 91 hostSocket.async_read_some( 92 boost::asio::buffer(outputBuffer), 93 [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 } 108 std::string_view payload(outputBuffer.data(), bytesRead); 109 self->conn.sendEx(crow::websocket::MessageType::Binary, payload, 110 std::bind_front(afterSendEx, weak_from_this())); 111 }); 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 121 if (ec) 122 { 123 BMCWEB_LOG_ERROR << "Failed to assign the DBUS socket" 124 << " Socket assign error: " << ec.message(); 125 return false; 126 } 127 128 conn.resumeRead(); 129 doWrite(); 130 doRead(); 131 return true; 132 } 133 134 boost::asio::local::stream_protocol::socket hostSocket; 135 136 std::array<char, 4096> outputBuffer{}; 137 138 std::string inputBuffer; 139 bool doingWrite = false; 140 crow::websocket::Connection& conn; 141 }; 142 143 using 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 148 inline ObmcConsoleMap& getConsoleHandlerMap() 149 { 150 static ObmcConsoleMap map; 151 return map; 152 } 153 154 // Remove connection from the connection map and if connection map is empty 155 // then remove the handler from handlers map. 156 inline void onClose(crow::websocket::Connection& conn, const std::string& err) 157 { 158 BMCWEB_LOG_INFO << "Closing websocket. Reason: " << err; 159 160 auto iter = getConsoleHandlerMap().find(&conn); 161 if (iter == getConsoleHandlerMap().end()) 162 { 163 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn; 164 return; 165 } 166 BMCWEB_LOG_DEBUG << "Remove connection " << &conn << " from obmc console"; 167 168 // Removed last connection so remove the path 169 getConsoleHandlerMap().erase(iter); 170 } 171 172 inline void connectConsoleSocket(crow::websocket::Connection& conn, 173 const boost::system::error_code& ec, 174 const sdbusplus::message::unix_fd& unixfd) 175 { 176 if (ec) 177 { 178 BMCWEB_LOG_ERROR << "Failed to call console Connect() method" 179 << " DBUS error: " << ec.message(); 180 conn.close("Failed to connect"); 181 return; 182 } 183 184 // Look up the handler 185 auto iter = getConsoleHandlerMap().find(&conn); 186 if (iter == getConsoleHandlerMap().end()) 187 { 188 BMCWEB_LOG_ERROR << "Failed to find the handler"; 189 conn.close("Internal error"); 190 return; 191 } 192 193 int fd = dup(unixfd); 194 if (fd == -1) 195 { 196 BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd" 197 << " error: " << strerror(errno); 198 conn.close("Internal error"); 199 return; 200 } 201 202 BMCWEB_LOG_DEBUG << "Console web socket path: " << conn.req.target() 203 << " Console unix FD: " << unixfd << " duped FD: " << fd; 204 205 if (!iter->second->connect(fd)) 206 { 207 close(fd); 208 conn.close("Internal Error"); 209 } 210 } 211 212 // Query consoles from DBUS and find the matching to the 213 // rules string. 214 inline void onOpen(crow::websocket::Connection& conn) 215 { 216 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 217 218 if (getConsoleHandlerMap().size() >= maxSessions) 219 { 220 conn.close("Max sessions are already connected"); 221 return; 222 } 223 224 std::shared_ptr<ConsoleHandler> handler = 225 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn); 226 getConsoleHandlerMap().emplace(&conn, handler); 227 228 conn.deferRead(); 229 230 // 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"); 247 } 248 249 inline 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 262 inline void requestRoutes(App& app) 263 { 264 BMCWEB_ROUTE(app, "/console0") 265 .privileges({{"OpenBMCHostConsole"}}) 266 .websocket() 267 .onopen(onOpen) 268 .onclose(onClose) 269 .onmessage(onMessage); 270 } 271 } // namespace obmc_console 272 } // namespace crow 273