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 << "Connection was already closed"; 189 return; 190 } 191 192 int fd = dup(unixfd); 193 if (fd == -1) 194 { 195 BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd" 196 << " error: " << strerror(errno); 197 conn.close("Internal error"); 198 return; 199 } 200 201 BMCWEB_LOG_DEBUG << "Console unix FD: " << unixfd << " duped FD: " << fd; 202 203 if (!iter->second->connect(fd)) 204 { 205 close(fd); 206 conn.close("Internal Error"); 207 } 208 } 209 210 inline void 211 processConsoleObject(crow::websocket::Connection& conn, 212 const std::string& consoleObjPath, 213 const boost::system::error_code& ec, 214 const ::dbus::utility::MapperGetObject& objInfo) 215 { 216 // Look up the handler 217 auto iter = getConsoleHandlerMap().find(&conn); 218 if (iter == getConsoleHandlerMap().end()) 219 { 220 BMCWEB_LOG_ERROR << "Connection was already closed"; 221 return; 222 } 223 224 if (ec) 225 { 226 BMCWEB_LOG_WARNING << "getDbusObject() for consoles failed. DBUS error:" 227 << ec.message(); 228 conn.close("getDbusObject() for consoles failed."); 229 return; 230 } 231 232 const auto valueIface = objInfo.begin(); 233 if (valueIface == objInfo.end()) 234 { 235 BMCWEB_LOG_WARNING << "getDbusObject() returned unexpected size: " 236 << objInfo.size(); 237 conn.close("getDbusObject() returned unexpected size"); 238 return; 239 } 240 241 const std::string& consoleService = valueIface->first; 242 BMCWEB_LOG_DEBUG << "Looking up unixFD for Service " << consoleService 243 << " Path " << consoleObjPath; 244 // Call Connect() method to get the unix FD 245 crow::connections::systemBus->async_method_call( 246 [&conn](const boost::system::error_code& ec1, 247 const sdbusplus::message::unix_fd& unixfd) { 248 connectConsoleSocket(conn, ec1, unixfd); 249 }, 250 consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access", 251 "Connect"); 252 } 253 254 // Query consoles from DBUS and find the matching to the 255 // rules string. 256 inline void onOpen(crow::websocket::Connection& conn) 257 { 258 std::string consoleLeaf; 259 260 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 261 262 if (getConsoleHandlerMap().size() >= maxSessions) 263 { 264 conn.close("Max sessions are already connected"); 265 return; 266 } 267 268 std::shared_ptr<ConsoleHandler> handler = 269 std::make_shared<ConsoleHandler>(conn.getIoContext(), conn); 270 getConsoleHandlerMap().emplace(&conn, handler); 271 272 conn.deferRead(); 273 274 // Keep old path for backward compatibility 275 if (conn.url().path() == "/console0") 276 { 277 consoleLeaf = "default"; 278 } 279 else 280 { 281 // Get the console id from console router path and prepare the console 282 // object path and console service. 283 consoleLeaf = conn.url().segments().back(); 284 } 285 std::string consolePath = 286 sdbusplus::message::object_path("/xyz/openbmc_project/console") / 287 consoleLeaf; 288 289 BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath 290 << " Request target = " << conn.url().path(); 291 292 // mapper call lambda 293 constexpr std::array<std::string_view, 1> interfaces = { 294 "xyz.openbmc_project.Console.Access"}; 295 296 dbus::utility::getDbusObject( 297 consolePath, interfaces, 298 [&conn, consolePath](const boost::system::error_code& ec, 299 const ::dbus::utility::MapperGetObject& objInfo) { 300 processConsoleObject(conn, consolePath, ec, objInfo); 301 }); 302 } 303 304 inline void onMessage(crow::websocket::Connection& conn, 305 const std::string& data, bool /*isBinary*/) 306 { 307 auto handler = getConsoleHandlerMap().find(&conn); 308 if (handler == getConsoleHandlerMap().end()) 309 { 310 BMCWEB_LOG_CRITICAL << "Unable to find connection " << &conn; 311 return; 312 } 313 handler->second->inputBuffer += data; 314 handler->second->doWrite(); 315 } 316 317 inline void requestRoutes(App& app) 318 { 319 BMCWEB_ROUTE(app, "/console0") 320 .privileges({{"OpenBMCHostConsole"}}) 321 .websocket() 322 .onopen(onOpen) 323 .onclose(onClose) 324 .onmessage(onMessage); 325 326 BMCWEB_ROUTE(app, "/console/<str>") 327 .privileges({{"OpenBMCHostConsole"}}) 328 .websocket() 329 .onopen(onOpen) 330 .onclose(onClose) 331 .onmessage(onMessage); 332 } 333 } // namespace obmc_console 334 } // namespace crow 335