1 #pragma once 2 #include <app.h> 3 #include <sys/socket.h> 4 #include <websocket.h> 5 6 #include <async_resp.hpp> 7 #include <boost/container/flat_map.hpp> 8 9 namespace crow 10 { 11 namespace obmc_kvm 12 { 13 14 static constexpr const uint maxSessions = 4; 15 16 class KvmSession 17 { 18 public: 19 explicit KvmSession(crow::websocket::Connection& connIn) : 20 conn(connIn), hostSocket(conn.get_io_context()), doingWrite(false) 21 { 22 boost::asio::ip::tcp::endpoint endpoint( 23 boost::asio::ip::make_address("127.0.0.1"), 5900); 24 hostSocket.async_connect( 25 endpoint, [this, &connIn](const boost::system::error_code& ec) { 26 if (ec) 27 { 28 BMCWEB_LOG_ERROR 29 << "conn:" << &conn 30 << ", Couldn't connect to KVM socket port: " << ec; 31 if (ec != boost::asio::error::operation_aborted) 32 { 33 connIn.close("Error in connecting to KVM port"); 34 } 35 return; 36 } 37 38 doRead(); 39 }); 40 } 41 42 void onMessage(const std::string& data) 43 { 44 if (data.length() > inputBuffer.capacity()) 45 { 46 BMCWEB_LOG_ERROR << "conn:" << &conn 47 << ", Buffer overrun when writing " 48 << data.length() << " bytes"; 49 conn.close("Buffer overrun"); 50 return; 51 } 52 53 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Read " << data.size() 54 << " bytes from websocket"; 55 boost::asio::buffer_copy(inputBuffer.prepare(data.size()), 56 boost::asio::buffer(data)); 57 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Committing " << data.size() 58 << " bytes from websocket"; 59 inputBuffer.commit(data.size()); 60 61 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", inputbuffer size " 62 << inputBuffer.size(); 63 doWrite(); 64 } 65 66 protected: 67 void doRead() 68 { 69 std::size_t bytes = outputBuffer.capacity() - outputBuffer.size(); 70 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Reading " << bytes 71 << " from kvm socket"; 72 hostSocket.async_read_some( 73 outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), 74 [this](const boost::system::error_code& ec, std::size_t bytesRead) { 75 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", read done. Read " 76 << bytesRead << " bytes"; 77 if (ec) 78 { 79 BMCWEB_LOG_ERROR 80 << "conn:" << &conn 81 << ", Couldn't read from KVM socket port: " << ec; 82 if (ec != boost::asio::error::operation_aborted) 83 { 84 conn.close("Error in connecting to KVM port"); 85 } 86 return; 87 } 88 89 outputBuffer.commit(bytesRead); 90 std::string_view payload( 91 static_cast<const char*>(outputBuffer.data().data()), 92 bytesRead); 93 BMCWEB_LOG_DEBUG << "conn:" << &conn 94 << ", Sending payload size " << payload.size(); 95 conn.sendBinary(payload); 96 outputBuffer.consume(bytesRead); 97 98 doRead(); 99 }); 100 } 101 102 void doWrite() 103 { 104 if (doingWrite) 105 { 106 BMCWEB_LOG_DEBUG << "conn:" << &conn 107 << ", Already writing. Bailing out"; 108 return; 109 } 110 if (inputBuffer.size() == 0) 111 { 112 BMCWEB_LOG_DEBUG << "conn:" << &conn 113 << ", inputBuffer empty. Bailing out"; 114 return; 115 } 116 117 doingWrite = true; 118 hostSocket.async_write_some( 119 inputBuffer.data(), [this](const boost::system::error_code& ec, 120 std::size_t bytesWritten) { 121 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote " 122 << bytesWritten << "bytes"; 123 doingWrite = false; 124 inputBuffer.consume(bytesWritten); 125 126 if (ec == boost::asio::error::eof) 127 { 128 conn.close("KVM socket port closed"); 129 return; 130 } 131 if (ec) 132 { 133 BMCWEB_LOG_ERROR << "conn:" << &conn 134 << ", Error in KVM socket write " << ec; 135 if (ec != boost::asio::error::operation_aborted) 136 { 137 conn.close("Error in reading to host port"); 138 } 139 return; 140 } 141 142 doWrite(); 143 }); 144 } 145 146 crow::websocket::Connection& conn; 147 boost::asio::ip::tcp::socket hostSocket; 148 boost::beast::flat_static_buffer<1024U * 50U> outputBuffer; 149 boost::beast::flat_static_buffer<1024U> inputBuffer; 150 bool doingWrite; 151 }; 152 153 static boost::container::flat_map<crow::websocket::Connection*, 154 std::unique_ptr<KvmSession>> 155 sessions; 156 157 inline void requestRoutes(App& app) 158 { 159 sessions.reserve(maxSessions); 160 161 BMCWEB_ROUTE(app, "/kvm/0") 162 .privileges({"ConfigureComponents", "ConfigureManager"}) 163 .websocket() 164 .onopen([](crow::websocket::Connection& conn, 165 std::shared_ptr<bmcweb::AsyncResp>) { 166 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 167 168 if (sessions.size() == maxSessions) 169 { 170 conn.close("Max sessions are already connected"); 171 return; 172 } 173 174 sessions[&conn] = std::make_unique<KvmSession>(conn); 175 }) 176 .onclose([](crow::websocket::Connection& conn, const std::string&) { 177 sessions.erase(&conn); 178 }) 179 .onmessage([](crow::websocket::Connection& conn, 180 const std::string& data, bool) { 181 if (sessions[&conn]) 182 { 183 sessions[&conn]->onMessage(data); 184 } 185 }); 186 } 187 188 } // namespace obmc_kvm 189 } // namespace crow 190