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