xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision d98a2f9388b9cab1100d30de401da43c32c98ef4)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
33eb2f35fSEd Tanous #pragma once
43ccb3adbSEd Tanous #include "app.hpp"
5*d98a2f93SEd Tanous #include "io_context_singleton.hpp"
6d7857201SEd Tanous #include "logging.hpp"
7faf100f9SEd Tanous #include "websocket.hpp"
83ccb3adbSEd Tanous 
9d7857201SEd Tanous #include <sys/types.h>
103eb2f35fSEd Tanous 
11d7857201SEd Tanous #include <boost/asio/buffer.hpp>
12d7857201SEd Tanous #include <boost/asio/error.hpp>
13d7857201SEd Tanous #include <boost/asio/ip/address.hpp>
14d7857201SEd Tanous #include <boost/asio/ip/tcp.hpp>
15d7857201SEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
163eb2f35fSEd Tanous #include <boost/container/flat_map.hpp>
173eb2f35fSEd Tanous 
18d7857201SEd Tanous #include <cstddef>
19d7857201SEd Tanous #include <memory>
20d7857201SEd Tanous #include <string>
21d7857201SEd Tanous 
223eb2f35fSEd Tanous namespace crow
233eb2f35fSEd Tanous {
243eb2f35fSEd Tanous namespace obmc_kvm
253eb2f35fSEd Tanous {
263eb2f35fSEd Tanous 
27a133b291SJae Hyun Yoo static constexpr const uint maxSessions = 4;
283eb2f35fSEd Tanous 
298e73b906SXinnan Xie class KvmSession : public std::enable_shared_from_this<KvmSession>
30c68604bdSJae Hyun Yoo {
31a133b291SJae Hyun Yoo   public:
KvmSession(crow::websocket::Connection & connIn)3223a21a1cSEd Tanous     explicit KvmSession(crow::websocket::Connection& connIn) :
33*d98a2f93SEd Tanous         conn(connIn), hostSocket(getIoContext())
343eb2f35fSEd Tanous     {
353eb2f35fSEd Tanous         boost::asio::ip::tcp::endpoint endpoint(
36b8c7eb23SJohnathan Mantey             boost::asio::ip::make_address("127.0.0.1"), 5900);
37a133b291SJae Hyun Yoo         hostSocket.async_connect(
3823a21a1cSEd Tanous             endpoint, [this, &connIn](const boost::system::error_code& ec) {
39a133b291SJae Hyun Yoo                 if (ec)
40a133b291SJae Hyun Yoo                 {
4162598e31SEd Tanous                     BMCWEB_LOG_ERROR(
4262598e31SEd Tanous                         "conn:{}, Couldn't connect to KVM socket port: {}",
4362598e31SEd Tanous                         logPtr(&conn), ec);
44a133b291SJae Hyun Yoo                     if (ec != boost::asio::error::operation_aborted)
45a133b291SJae Hyun Yoo                     {
4623a21a1cSEd Tanous                         connIn.close("Error in connecting to KVM port");
473eb2f35fSEd Tanous                     }
48a133b291SJae Hyun Yoo                     return;
49a133b291SJae Hyun Yoo                 }
50a133b291SJae Hyun Yoo 
51a133b291SJae Hyun Yoo                 doRead();
52a133b291SJae Hyun Yoo             });
53a133b291SJae Hyun Yoo     }
54a133b291SJae Hyun Yoo 
onMessage(const std::string & data)55a133b291SJae Hyun Yoo     void onMessage(const std::string& data)
56a133b291SJae Hyun Yoo     {
573eb2f35fSEd Tanous         if (data.length() > inputBuffer.capacity())
583eb2f35fSEd Tanous         {
5962598e31SEd Tanous             BMCWEB_LOG_ERROR("conn:{}, Buffer overrun when writing {} bytes",
6062598e31SEd Tanous                              logPtr(&conn), data.length());
613eb2f35fSEd Tanous             conn.close("Buffer overrun");
623eb2f35fSEd Tanous             return;
633eb2f35fSEd Tanous         }
643eb2f35fSEd Tanous 
6562598e31SEd Tanous         BMCWEB_LOG_DEBUG("conn:{}, Read {} bytes from websocket", logPtr(&conn),
6662598e31SEd Tanous                          data.size());
6744106f34SEd Tanous         size_t copied = boost::asio::buffer_copy(
6844106f34SEd Tanous             inputBuffer.prepare(data.size()), boost::asio::buffer(data));
6962598e31SEd Tanous         BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket",
7044106f34SEd Tanous                          logPtr(&conn), copied);
7144106f34SEd Tanous         inputBuffer.commit(copied);
723eb2f35fSEd Tanous 
7362598e31SEd Tanous         BMCWEB_LOG_DEBUG("conn:{}, inputbuffer size {}", logPtr(&conn),
7462598e31SEd Tanous                          inputBuffer.size());
75a133b291SJae Hyun Yoo         doWrite();
76a133b291SJae Hyun Yoo     }
77a133b291SJae Hyun Yoo 
78a133b291SJae Hyun Yoo   protected:
doRead()79a133b291SJae Hyun Yoo     void doRead()
80a133b291SJae Hyun Yoo     {
81a133b291SJae Hyun Yoo         std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
8262598e31SEd Tanous         BMCWEB_LOG_DEBUG("conn:{}, Reading {} from kvm socket", logPtr(&conn),
8362598e31SEd Tanous                          bytes);
84a133b291SJae Hyun Yoo         hostSocket.async_read_some(
85a133b291SJae Hyun Yoo             outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
868e73b906SXinnan Xie             [this, weak(weak_from_this())](const boost::system::error_code& ec,
878e73b906SXinnan Xie                                            std::size_t bytesRead) {
888e73b906SXinnan Xie                 auto self = weak.lock();
898e73b906SXinnan Xie                 if (self == nullptr)
908e73b906SXinnan Xie                 {
918e73b906SXinnan Xie                     return;
928e73b906SXinnan Xie                 }
9362598e31SEd Tanous                 BMCWEB_LOG_DEBUG("conn:{}, read done.  Read {} bytes",
9462598e31SEd Tanous                                  logPtr(&conn), bytesRead);
95a133b291SJae Hyun Yoo                 if (ec)
96a133b291SJae Hyun Yoo                 {
9762598e31SEd Tanous                     BMCWEB_LOG_ERROR(
9862598e31SEd Tanous                         "conn:{}, Couldn't read from KVM socket port: {}",
9962598e31SEd Tanous                         logPtr(&conn), ec);
100a133b291SJae Hyun Yoo                     if (ec != boost::asio::error::operation_aborted)
101a133b291SJae Hyun Yoo                     {
102a133b291SJae Hyun Yoo                         conn.close("Error in connecting to KVM port");
103a133b291SJae Hyun Yoo                     }
104a133b291SJae Hyun Yoo                     return;
105a133b291SJae Hyun Yoo                 }
106a133b291SJae Hyun Yoo 
107a133b291SJae Hyun Yoo                 outputBuffer.commit(bytesRead);
108a133b291SJae Hyun Yoo                 std::string_view payload(
109a133b291SJae Hyun Yoo                     static_cast<const char*>(outputBuffer.data().data()),
110a133b291SJae Hyun Yoo                     bytesRead);
111bd79bce8SPatrick Williams                 BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}",
112bd79bce8SPatrick Williams                                  logPtr(&conn), payload.size());
113a133b291SJae Hyun Yoo                 conn.sendBinary(payload);
114a133b291SJae Hyun Yoo                 outputBuffer.consume(bytesRead);
115a133b291SJae Hyun Yoo 
116a133b291SJae Hyun Yoo                 doRead();
117a133b291SJae Hyun Yoo             });
118a133b291SJae Hyun Yoo     }
119a133b291SJae Hyun Yoo 
doWrite()120a133b291SJae Hyun Yoo     void doWrite()
121a133b291SJae Hyun Yoo     {
122a133b291SJae Hyun Yoo         if (doingWrite)
123a133b291SJae Hyun Yoo         {
12462598e31SEd Tanous             BMCWEB_LOG_DEBUG("conn:{}, Already writing.  Bailing out",
12562598e31SEd Tanous                              logPtr(&conn));
126a133b291SJae Hyun Yoo             return;
127a133b291SJae Hyun Yoo         }
128a133b291SJae Hyun Yoo         if (inputBuffer.size() == 0)
129a133b291SJae Hyun Yoo         {
13062598e31SEd Tanous             BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty.  Bailing out",
13162598e31SEd Tanous                              logPtr(&conn));
132a133b291SJae Hyun Yoo             return;
133a133b291SJae Hyun Yoo         }
134a133b291SJae Hyun Yoo 
135a133b291SJae Hyun Yoo         doingWrite = true;
1368e73b906SXinnan Xie         hostSocket.async_write_some(
1378e73b906SXinnan Xie             inputBuffer.data(),
1388e73b906SXinnan Xie             [this, weak(weak_from_this())](const boost::system::error_code& ec,
139a133b291SJae Hyun Yoo                                            std::size_t bytesWritten) {
1408e73b906SXinnan Xie                 auto self = weak.lock();
1418e73b906SXinnan Xie                 if (self == nullptr)
1428e73b906SXinnan Xie                 {
1438e73b906SXinnan Xie                     return;
1448e73b906SXinnan Xie                 }
14562598e31SEd Tanous                 BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn),
14662598e31SEd Tanous                                  bytesWritten);
147a133b291SJae Hyun Yoo                 doingWrite = false;
148a133b291SJae Hyun Yoo                 inputBuffer.consume(bytesWritten);
149a133b291SJae Hyun Yoo 
150a133b291SJae Hyun Yoo                 if (ec == boost::asio::error::eof)
151a133b291SJae Hyun Yoo                 {
152a133b291SJae Hyun Yoo                     conn.close("KVM socket port closed");
153a133b291SJae Hyun Yoo                     return;
154a133b291SJae Hyun Yoo                 }
155a133b291SJae Hyun Yoo                 if (ec)
156a133b291SJae Hyun Yoo                 {
15762598e31SEd Tanous                     BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}",
15862598e31SEd Tanous                                      logPtr(&conn), ec);
159a133b291SJae Hyun Yoo                     if (ec != boost::asio::error::operation_aborted)
160a133b291SJae Hyun Yoo                     {
161a133b291SJae Hyun Yoo                         conn.close("Error in reading to host port");
162a133b291SJae Hyun Yoo                     }
163a133b291SJae Hyun Yoo                     return;
164a133b291SJae Hyun Yoo                 }
165a133b291SJae Hyun Yoo 
1663eb2f35fSEd Tanous                 doWrite();
1673eb2f35fSEd Tanous             });
1683eb2f35fSEd Tanous     }
169a133b291SJae Hyun Yoo 
170a133b291SJae Hyun Yoo     crow::websocket::Connection& conn;
171a133b291SJae Hyun Yoo     boost::asio::ip::tcp::socket hostSocket;
1726de264ccSEd Tanous     boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer;
1736de264ccSEd Tanous     boost::beast::flat_static_buffer<1024UL> inputBuffer;
174f5b191a6SEd Tanous     bool doingWrite{false};
175a133b291SJae Hyun Yoo };
176a133b291SJae Hyun Yoo 
177cf9e417dSEd Tanous using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
1788e73b906SXinnan Xie                                               std::shared_ptr<KvmSession>>;
179cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
180cf9e417dSEd Tanous static SessionMap sessions;
181a133b291SJae Hyun Yoo 
requestRoutes(App & app)18252cc112dSEd Tanous inline void requestRoutes(App& app)
183a133b291SJae Hyun Yoo {
184a133b291SJae Hyun Yoo     sessions.reserve(maxSessions);
185a133b291SJae Hyun Yoo 
186a133b291SJae Hyun Yoo     BMCWEB_ROUTE(app, "/kvm/0")
187432a890cSEd Tanous         .privileges({{"ConfigureComponents", "ConfigureManager"}})
188a133b291SJae Hyun Yoo         .websocket()
1897772638eSzhanghch05         .onopen([](crow::websocket::Connection& conn) {
19062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
191a133b291SJae Hyun Yoo 
192a133b291SJae Hyun Yoo             if (sessions.size() == maxSessions)
193a133b291SJae Hyun Yoo             {
194a133b291SJae Hyun Yoo                 conn.close("Max sessions are already connected");
195a133b291SJae Hyun Yoo                 return;
196a133b291SJae Hyun Yoo             }
197a133b291SJae Hyun Yoo 
1988e73b906SXinnan Xie             sessions[&conn] = std::make_shared<KvmSession>(conn);
199a133b291SJae Hyun Yoo         })
200cb13a392SEd Tanous         .onclose([](crow::websocket::Connection& conn, const std::string&) {
201cb13a392SEd Tanous             sessions.erase(&conn);
202cb13a392SEd Tanous         })
203a133b291SJae Hyun Yoo         .onmessage([](crow::websocket::Connection& conn,
204cb13a392SEd Tanous                       const std::string& data, bool) {
205a133b291SJae Hyun Yoo             if (sessions[&conn])
206a133b291SJae Hyun Yoo             {
207a133b291SJae Hyun Yoo                 sessions[&conn]->onMessage(data);
208a133b291SJae Hyun Yoo             }
209a133b291SJae Hyun Yoo         });
210a133b291SJae Hyun Yoo }
211a133b291SJae Hyun Yoo 
2123eb2f35fSEd Tanous } // namespace obmc_kvm
2133eb2f35fSEd Tanous } // namespace crow
214