xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision e7245fe8)
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
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](const boost::system::error_code& ec, std::size_t bytesRead) {
75             BMCWEB_LOG_DEBUG("conn:{}, read done.  Read {} bytes",
76                              logPtr(&conn), bytesRead);
77             if (ec)
78             {
79                 BMCWEB_LOG_ERROR(
80                     "conn:{}, Couldn't read from KVM socket port: {}",
81                     logPtr(&conn), ec);
82                 if (ec != boost::asio::error::operation_aborted)
83                 {
84                     conn.close("Error in connecting to KVM port");
85                 }
86                 return;
87             }
88 
89             outputBuffer.commit(bytesRead);
90             std::string_view payload(
91                 static_cast<const char*>(outputBuffer.data().data()),
92                 bytesRead);
93             BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}", logPtr(&conn),
94                              payload.size());
95             conn.sendBinary(payload);
96             outputBuffer.consume(bytesRead);
97 
98             doRead();
99             });
100     }
101 
102     void doWrite()
103     {
104         if (doingWrite)
105         {
106             BMCWEB_LOG_DEBUG("conn:{}, Already writing.  Bailing out",
107                              logPtr(&conn));
108             return;
109         }
110         if (inputBuffer.size() == 0)
111         {
112             BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty.  Bailing out",
113                              logPtr(&conn));
114             return;
115         }
116 
117         doingWrite = true;
118         hostSocket.async_write_some(inputBuffer.data(),
119                                     [this](const boost::system::error_code& ec,
120                                            std::size_t bytesWritten) {
121             BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn),
122                              bytesWritten);
123             doingWrite = false;
124             inputBuffer.consume(bytesWritten);
125 
126             if (ec == boost::asio::error::eof)
127             {
128                 conn.close("KVM socket port closed");
129                 return;
130             }
131             if (ec)
132             {
133                 BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}",
134                                  logPtr(&conn), ec);
135                 if (ec != boost::asio::error::operation_aborted)
136                 {
137                     conn.close("Error in reading to host port");
138                 }
139                 return;
140             }
141 
142             doWrite();
143         });
144     }
145 
146     crow::websocket::Connection& conn;
147     boost::asio::ip::tcp::socket hostSocket;
148     boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer;
149     boost::beast::flat_static_buffer<1024UL> inputBuffer;
150     bool doingWrite{false};
151 };
152 
153 using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
154                                               std::unique_ptr<KvmSession>>;
155 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
156 static SessionMap sessions;
157 
158 inline void requestRoutes(App& app)
159 {
160     sessions.reserve(maxSessions);
161 
162     BMCWEB_ROUTE(app, "/kvm/0")
163         .privileges({{"ConfigureComponents", "ConfigureManager"}})
164         .websocket()
165         .onopen([](crow::websocket::Connection& conn) {
166             BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
167 
168             if (sessions.size() == maxSessions)
169             {
170                 conn.close("Max sessions are already connected");
171                 return;
172             }
173 
174             sessions[&conn] = std::make_unique<KvmSession>(conn);
175         })
176         .onclose([](crow::websocket::Connection& conn, const std::string&) {
177             sessions.erase(&conn);
178         })
179         .onmessage([](crow::websocket::Connection& conn,
180                       const std::string& data, bool) {
181             if (sessions[&conn])
182             {
183                 sessions[&conn]->onMessage(data);
184             }
185         });
186 }
187 
188 } // namespace obmc_kvm
189 } // namespace crow
190