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