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