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