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