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:
KvmSession(crow::websocket::Connection & connIn)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
onMessage(const std::string & data)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:
doRead()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
doWrite()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
requestRoutes(App & app)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