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/container/flat_map.hpp> 9 10 namespace crow 11 { 12 namespace obmc_kvm 13 { 14 15 static constexpr const uint maxSessions = 4; 16 17 class KvmSession : public std::enable_shared_from_this<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:{}, Couldn't connect to KVM socket port: {}", 31 logPtr(&conn), 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:{}, Buffer overrun when writing {} bytes", 48 logPtr(&conn), data.length()); 49 conn.close("Buffer overrun"); 50 return; 51 } 52 53 BMCWEB_LOG_DEBUG("conn:{}, Read {} bytes from websocket", logPtr(&conn), 54 data.size()); 55 size_t copied = boost::asio::buffer_copy( 56 inputBuffer.prepare(data.size()), boost::asio::buffer(data)); 57 BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket", 58 logPtr(&conn), copied); 59 inputBuffer.commit(copied); 60 61 BMCWEB_LOG_DEBUG("conn:{}, inputbuffer size {}", logPtr(&conn), 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:{}, Reading {} from kvm socket", logPtr(&conn), 71 bytes); 72 hostSocket.async_read_some( 73 outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), 74 [this, weak(weak_from_this())](const boost::system::error_code& ec, 75 std::size_t bytesRead) { 76 auto self = weak.lock(); 77 if (self == nullptr) 78 { 79 return; 80 } 81 BMCWEB_LOG_DEBUG("conn:{}, read done. Read {} bytes", 82 logPtr(&conn), bytesRead); 83 if (ec) 84 { 85 BMCWEB_LOG_ERROR( 86 "conn:{}, Couldn't read from KVM socket port: {}", 87 logPtr(&conn), ec); 88 if (ec != boost::asio::error::operation_aborted) 89 { 90 conn.close("Error in connecting to KVM port"); 91 } 92 return; 93 } 94 95 outputBuffer.commit(bytesRead); 96 std::string_view payload( 97 static_cast<const char*>(outputBuffer.data().data()), 98 bytesRead); 99 BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}", logPtr(&conn), 100 payload.size()); 101 conn.sendBinary(payload); 102 outputBuffer.consume(bytesRead); 103 104 doRead(); 105 }); 106 } 107 108 void doWrite() 109 { 110 if (doingWrite) 111 { 112 BMCWEB_LOG_DEBUG("conn:{}, Already writing. Bailing out", 113 logPtr(&conn)); 114 return; 115 } 116 if (inputBuffer.size() == 0) 117 { 118 BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty. Bailing out", 119 logPtr(&conn)); 120 return; 121 } 122 123 doingWrite = true; 124 hostSocket.async_write_some( 125 inputBuffer.data(), 126 [this, weak(weak_from_this())](const boost::system::error_code& ec, 127 std::size_t bytesWritten) { 128 auto self = weak.lock(); 129 if (self == nullptr) 130 { 131 return; 132 } 133 BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn), 134 bytesWritten); 135 doingWrite = false; 136 inputBuffer.consume(bytesWritten); 137 138 if (ec == boost::asio::error::eof) 139 { 140 conn.close("KVM socket port closed"); 141 return; 142 } 143 if (ec) 144 { 145 BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}", 146 logPtr(&conn), ec); 147 if (ec != boost::asio::error::operation_aborted) 148 { 149 conn.close("Error in reading to host port"); 150 } 151 return; 152 } 153 154 doWrite(); 155 }); 156 } 157 158 crow::websocket::Connection& conn; 159 boost::asio::ip::tcp::socket hostSocket; 160 boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer; 161 boost::beast::flat_static_buffer<1024UL> inputBuffer; 162 bool doingWrite{false}; 163 }; 164 165 using SessionMap = boost::container::flat_map<crow::websocket::Connection*, 166 std::shared_ptr<KvmSession>>; 167 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 168 static SessionMap sessions; 169 170 inline void requestRoutes(App& app) 171 { 172 sessions.reserve(maxSessions); 173 174 BMCWEB_ROUTE(app, "/kvm/0") 175 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 176 .websocket() 177 .onopen([](crow::websocket::Connection& conn) { 178 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); 179 180 if (sessions.size() == maxSessions) 181 { 182 conn.close("Max sessions are already connected"); 183 return; 184 } 185 186 sessions[&conn] = std::make_shared<KvmSession>(conn); 187 }) 188 .onclose([](crow::websocket::Connection& conn, const std::string&) { 189 sessions.erase(&conn); 190 }) 191 .onmessage([](crow::websocket::Connection& conn, 192 const std::string& data, bool) { 193 if (sessions[&conn]) 194 { 195 sessions[&conn]->onMessage(data); 196 } 197 }); 198 } 199 200 } // namespace obmc_kvm 201 } // namespace crow 202