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 {}", ec.message()); 71 return; 72 } 73 self->doWrite(); 74 }); 75 } 76 77 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak) 78 { 79 std::shared_ptr<ConsoleHandler> self = weak.lock(); 80 if (self == nullptr) 81 { 82 return; 83 } 84 self->doRead(); 85 } 86 87 void doRead() 88 { 89 BMCWEB_LOG_DEBUG("Reading from socket"); 90 hostSocket.async_read_some( 91 boost::asio::buffer(outputBuffer), 92 [this, weakSelf(weak_from_this())]( 93 const boost::system::error_code& ec, std::size_t bytesRead) { 94 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead); 95 std::shared_ptr<ConsoleHandler> self = weakSelf.lock(); 96 if (self == nullptr) 97 { 98 return; 99 } 100 if (ec) 101 { 102 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}", 103 ec.message()); 104 conn.close("Error connecting to host port"); 105 return; 106 } 107 std::string_view payload(outputBuffer.data(), bytesRead); 108 self->conn.sendEx(crow::websocket::MessageType::Binary, payload, 109 std::bind_front(afterSendEx, weak_from_this())); 110 }); 111 } 112 113 bool connect(int fd) 114 { 115 boost::system::error_code ec; 116 boost::asio::local::stream_protocol proto; 117 118 hostSocket.assign(proto, fd, ec); 119 120 if (ec) 121 { 122 BMCWEB_LOG_ERROR( 123 "Failed to assign the DBUS socket Socket assign error: {}", 124 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 {}", logPtr(&conn)); 164 return; 165 } 166 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn)); 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( 179 "Failed to call console Connect() method DBUS error: {}", 180 ec.message()); 181 conn.close("Failed to connect"); 182 return; 183 } 184 185 // Look up the handler 186 auto iter = getConsoleHandlerMap().find(&conn); 187 if (iter == getConsoleHandlerMap().end()) 188 { 189 BMCWEB_LOG_ERROR("Connection was already closed"); 190 return; 191 } 192 193 int fd = dup(unixfd); 194 if (fd == -1) 195 { 196 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error: {}", 197 strerror(errno)); 198 conn.close("Internal error"); 199 return; 200 } 201 202 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd); 203 204 if (!iter->second->connect(fd)) 205 { 206 close(fd); 207 conn.close("Internal Error"); 208 } 209 } 210 211 inline void 212 processConsoleObject(crow::websocket::Connection& conn, 213 const std::string& consoleObjPath, 214 const boost::system::error_code& ec, 215 const ::dbus::utility::MapperGetObject& objInfo) 216 { 217 // Look up the handler 218 auto iter = getConsoleHandlerMap().find(&conn); 219 if (iter == getConsoleHandlerMap().end()) 220 { 221 BMCWEB_LOG_ERROR("Connection was already closed"); 222 return; 223 } 224 225 if (ec) 226 { 227 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}", 228 ec.message()); 229 conn.close("getDbusObject() for consoles failed."); 230 return; 231 } 232 233 const auto valueIface = objInfo.begin(); 234 if (valueIface == objInfo.end()) 235 { 236 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}", 237 objInfo.size()); 238 conn.close("getDbusObject() returned unexpected size"); 239 return; 240 } 241 242 const std::string& consoleService = valueIface->first; 243 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService, 244 consoleObjPath); 245 // Call Connect() method to get the unix FD 246 crow::connections::systemBus->async_method_call( 247 [&conn](const boost::system::error_code& ec1, 248 const sdbusplus::message::unix_fd& unixfd) { 249 connectConsoleSocket(conn, ec1, unixfd); 250 }, 251 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access", 252 "Connect"); 253 } 254 255 // Query consoles from DBUS and find the matching to the 256 // rules string. 257 inline void onOpen(crow::websocket::Connection& conn) 258 { 259 std::string consoleLeaf; 260 261 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); 262 263 if (getConsoleHandlerMap().size() >= maxSessions) 264 { 265 conn.close("Max sessions are already connected"); 266 return; 267 } 268 269 std::shared_ptr<ConsoleHandler> handler = 270 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn); 271 getConsoleHandlerMap().emplace(&conn, handler); 272 273 conn.deferRead(); 274 275 // Keep old path for backward compatibility 276 if (conn.url().path() == "/console0") 277 { 278 consoleLeaf = "default"; 279 } 280 else 281 { 282 // Get the console id from console router path and prepare the console 283 // object path and console service. 284 consoleLeaf = conn.url().segments().back(); 285 } 286 std::string consolePath = 287 sdbusplus::message::object_path("/xyz/openbmc_project/console") / 288 consoleLeaf; 289 290 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}", 291 consolePath, conn.url().path()); 292 293 // mapper call lambda 294 constexpr std::array<std::string_view, 1> interfaces = { 295 "xyz.openbmc_project.Console.Access"}; 296 297 dbus::utility::getDbusObject( 298 consolePath, interfaces, 299 [&conn, consolePath](const boost::system::error_code& ec, 300 const ::dbus::utility::MapperGetObject& objInfo) { 301 processConsoleObject(conn, consolePath, ec, objInfo); 302 }); 303 } 304 305 inline void onMessage(crow::websocket::Connection& conn, 306 const std::string& data, bool /*isBinary*/) 307 { 308 auto handler = getConsoleHandlerMap().find(&conn); 309 if (handler == getConsoleHandlerMap().end()) 310 { 311 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn)); 312 return; 313 } 314 handler->second->inputBuffer += data; 315 handler->second->doWrite(); 316 } 317 318 inline void requestRoutes(App& app) 319 { 320 BMCWEB_ROUTE(app, "/console0") 321 .privileges({{"OpenBMCHostConsole"}}) 322 .websocket() 323 .onopen(onOpen) 324 .onclose(onClose) 325 .onmessage(onMessage); 326 327 BMCWEB_ROUTE(app, "/console/<str>") 328 .privileges({{"OpenBMCHostConsole"}}) 329 .websocket() 330 .onopen(onOpen) 331 .onclose(onClose) 332 .onmessage(onMessage); 333 } 334 } // namespace obmc_console 335 } // namespace crow 336