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