xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision 9d33a47c)
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         boost::asio::buffer_copy(inputBuffer.prepare(data.size()),
56                                  boost::asio::buffer(data));
57         BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket",
58                          logPtr(&conn), data.size());
59         inputBuffer.commit(data.size());
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 {}", logPtr(&conn),
100                              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