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 strerror(errno)); 205 conn.close("Internal error"); 206 return; 207 } 208 209 BMCWEB_LOG_DEBUG("Console duped FD: {}", fd); 210 211 if (!iter->second->connect(fd)) 212 { 213 close(fd); 214 conn.close("Internal Error"); 215 } 216 } 217 218 inline void processConsoleObject( 219 crow::websocket::Connection& conn, 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