1 #pragma once 2 #include <sys/socket.h> 3 4 #include <app.hpp> 5 #include <async_resp.hpp> 6 #include <boost/container/flat_map.hpp> 7 #include <websocket.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.getIoContext()) 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 << ", Sending payload size " 94 << 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(inputBuffer.data(), 119 [this](const boost::system::error_code& ec, 120 std::size_t bytesWritten) { 121 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote " << bytesWritten 122 << "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<1024UL * 50UL> outputBuffer; 149 boost::beast::flat_static_buffer<1024UL> inputBuffer; 150 bool doingWrite{false}; 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 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 166 167 if (sessions.size() == maxSessions) 168 { 169 conn.close("Max sessions are already connected"); 170 return; 171 } 172 173 sessions[&conn] = std::make_unique<KvmSession>(conn); 174 }) 175 .onclose([](crow::websocket::Connection& conn, const std::string&) { 176 sessions.erase(&conn); 177 }) 178 .onmessage([](crow::websocket::Connection& conn, 179 const std::string& data, bool) { 180 if (sessions[&conn]) 181 { 182 sessions[&conn]->onMessage(data); 183 } 184 }); 185 } 186 187 } // namespace obmc_kvm 188 } // namespace crow 189