xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
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