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 #include <boost/system/error_code.hpp> 11 12 #include <array> 13 #include <memory> 14 #include <string> 15 #include <string_view> 16 17 namespace crow 18 { 19 namespace obmc_console 20 { 21 22 // Update this value each time we add new console route. 23 static constexpr const uint maxSessions = 32; 24 25 class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler> 26 { 27 public: 28 ConsoleHandler(boost::asio::io_context& ioc, 29 crow::websocket::Connection& connIn) : 30 hostSocket(ioc), 31 conn(connIn) 32 {} 33 34 ~ConsoleHandler() = default; 35 36 ConsoleHandler(const ConsoleHandler&) = delete; 37 ConsoleHandler(ConsoleHandler&&) = delete; 38 ConsoleHandler& operator=(const ConsoleHandler&) = delete; 39 ConsoleHandler& operator=(ConsoleHandler&&) = delete; 40 41 void doWrite() 42 { 43 if (doingWrite) 44 { 45 BMCWEB_LOG_DEBUG("Already writing. Bailing out"); 46 return; 47 } 48 49 if (inputBuffer.empty()) 50 { 51 BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out"); 52 return; 53 } 54 55 doingWrite = true; 56 hostSocket.async_write_some( 57 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), 58 [weak(weak_from_this())](const boost::beast::error_code& ec, 59 std::size_t bytesWritten) { 60 std::shared_ptr<ConsoleHandler> self = weak.lock(); 61 if (self == nullptr) 62 { 63 return; 64 } 65 66 self->doingWrite = false; 67 self->inputBuffer.erase(0, bytesWritten); 68 69 if (ec == boost::asio::error::eof) 70 { 71 self->conn.close("Error in reading to host port"); 72 return; 73 } 74 if (ec) 75 { 76 BMCWEB_LOG_ERROR("Error in host serial write {}", ec.message()); 77 return; 78 } 79 self->doWrite(); 80 }); 81 } 82 83 static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak) 84 { 85 std::shared_ptr<ConsoleHandler> self = weak.lock(); 86 if (self == nullptr) 87 { 88 return; 89 } 90 self->doRead(); 91 } 92 93 void doRead() 94 { 95 BMCWEB_LOG_DEBUG("Reading from socket"); 96 hostSocket.async_read_some( 97 boost::asio::buffer(outputBuffer), 98 [this, weakSelf(weak_from_this())]( 99 const boost::system::error_code& ec, std::size_t bytesRead) { 100 BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead); 101 std::shared_ptr<ConsoleHandler> self = weakSelf.lock(); 102 if (self == nullptr) 103 { 104 return; 105 } 106 if (ec) 107 { 108 BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}", 109 ec.message()); 110 conn.close("Error connecting to host port"); 111 return; 112 } 113 std::string_view payload(outputBuffer.data(), bytesRead); 114 self->conn.sendEx(crow::websocket::MessageType::Binary, payload, 115 std::bind_front(afterSendEx, weak_from_this())); 116 }); 117 } 118 119 bool connect(int fd) 120 { 121 boost::system::error_code ec; 122 boost::asio::local::stream_protocol proto; 123 124 hostSocket.assign(proto, fd, ec); 125 126 if (ec) 127 { 128 BMCWEB_LOG_ERROR( 129 "Failed to assign the DBUS socket Socket assign error: {}", 130 ec.message()); 131 return false; 132 } 133 134 conn.resumeRead(); 135 doWrite(); 136 doRead(); 137 return true; 138 } 139 140 boost::asio::local::stream_protocol::socket hostSocket; 141 142 std::array<char, 4096> outputBuffer{}; 143 144 std::string inputBuffer; 145 bool doingWrite = false; 146 crow::websocket::Connection& conn; 147 }; 148 149 using ObmcConsoleMap = boost::container::flat_map< 150 crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>, 151 std::vector<std::pair<crow::websocket::Connection*, 152 std::shared_ptr<ConsoleHandler>>>>; 153 154 inline ObmcConsoleMap& getConsoleHandlerMap() 155 { 156 static ObmcConsoleMap map; 157 return map; 158 } 159 160 // Remove connection from the connection map and if connection map is empty 161 // then remove the handler from handlers map. 162 inline void onClose(crow::websocket::Connection& conn, const std::string& err) 163 { 164 BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err); 165 166 auto iter = getConsoleHandlerMap().find(&conn); 167 if (iter == getConsoleHandlerMap().end()) 168 { 169 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn)); 170 return; 171 } 172 BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn)); 173 174 // Removed last connection so remove the path 175 getConsoleHandlerMap().erase(iter); 176 } 177 178 inline void connectConsoleSocket(crow::websocket::Connection& conn, 179 const boost::system::error_code& ec, 180 const sdbusplus::message::unix_fd& unixfd) 181 { 182 if (ec) 183 { 184 BMCWEB_LOG_ERROR( 185 "Failed to call console Connect() method DBUS error: {}", 186 ec.message()); 187 conn.close("Failed to connect"); 188 return; 189 } 190 191 // Look up the handler 192 auto iter = getConsoleHandlerMap().find(&conn); 193 if (iter == getConsoleHandlerMap().end()) 194 { 195 BMCWEB_LOG_ERROR("Connection was already closed"); 196 return; 197 } 198 199 int fd = dup(unixfd); 200 if (fd == -1) 201 { 202 BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error: {}", 203 strerror(errno)); 204 conn.close("Internal error"); 205 return; 206 } 207 208 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd); 209 210 if (!iter->second->connect(fd)) 211 { 212 close(fd); 213 conn.close("Internal Error"); 214 } 215 } 216 217 inline void 218 processConsoleObject(crow::websocket::Connection& conn, 219 const std::string& consoleObjPath, 220 const boost::system::error_code& ec, 221 const ::dbus::utility::MapperGetObject& objInfo) 222 { 223 // Look up the handler 224 auto iter = getConsoleHandlerMap().find(&conn); 225 if (iter == getConsoleHandlerMap().end()) 226 { 227 BMCWEB_LOG_ERROR("Connection was already closed"); 228 return; 229 } 230 231 if (ec) 232 { 233 BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}", 234 ec.message()); 235 conn.close("getDbusObject() for consoles failed."); 236 return; 237 } 238 239 const auto valueIface = objInfo.begin(); 240 if (valueIface == objInfo.end()) 241 { 242 BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}", 243 objInfo.size()); 244 conn.close("getDbusObject() returned unexpected size"); 245 return; 246 } 247 248 const std::string& consoleService = valueIface->first; 249 BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService, 250 consoleObjPath); 251 // Call Connect() method to get the unix FD 252 crow::connections::systemBus->async_method_call( 253 [&conn](const boost::system::error_code& ec1, 254 const sdbusplus::message::unix_fd& unixfd) { 255 connectConsoleSocket(conn, ec1, unixfd); 256 }, 257 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access", 258 "Connect"); 259 } 260 261 // Query consoles from DBUS and find the matching to the 262 // rules string. 263 inline void onOpen(crow::websocket::Connection& conn) 264 { 265 std::string consoleLeaf; 266 267 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); 268 269 if (getConsoleHandlerMap().size() >= maxSessions) 270 { 271 conn.close("Max sessions are already connected"); 272 return; 273 } 274 275 std::shared_ptr<ConsoleHandler> handler = 276 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn); 277 getConsoleHandlerMap().emplace(&conn, handler); 278 279 conn.deferRead(); 280 281 // Keep old path for backward compatibility 282 if (conn.url().path() == "/console0") 283 { 284 consoleLeaf = "default"; 285 } 286 else 287 { 288 // Get the console id from console router path and prepare the console 289 // object path and console service. 290 consoleLeaf = conn.url().segments().back(); 291 } 292 std::string consolePath = 293 sdbusplus::message::object_path("/xyz/openbmc_project/console") / 294 consoleLeaf; 295 296 BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}", 297 consolePath, conn.url().path()); 298 299 // mapper call lambda 300 constexpr std::array<std::string_view, 1> interfaces = { 301 "xyz.openbmc_project.Console.Access"}; 302 303 dbus::utility::getDbusObject( 304 consolePath, interfaces, 305 [&conn, consolePath](const boost::system::error_code& ec, 306 const ::dbus::utility::MapperGetObject& objInfo) { 307 processConsoleObject(conn, consolePath, ec, objInfo); 308 }); 309 } 310 311 inline void onMessage(crow::websocket::Connection& conn, 312 const std::string& data, bool /*isBinary*/) 313 { 314 auto handler = getConsoleHandlerMap().find(&conn); 315 if (handler == getConsoleHandlerMap().end()) 316 { 317 BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn)); 318 return; 319 } 320 handler->second->inputBuffer += data; 321 handler->second->doWrite(); 322 } 323 324 inline void requestRoutes(App& app) 325 { 326 BMCWEB_ROUTE(app, "/console0") 327 .privileges({{"OpenBMCHostConsole"}}) 328 .websocket() 329 .onopen(onOpen) 330 .onclose(onClose) 331 .onmessage(onMessage); 332 333 BMCWEB_ROUTE(app, "/console/<str>") 334 .privileges({{"OpenBMCHostConsole"}}) 335 .websocket() 336 .onopen(onOpen) 337 .onclose(onClose) 338 .onmessage(onMessage); 339 } 340 } // namespace obmc_console 341 } // namespace crow 342