From 36c0f2a35e670a4b798b7b42fd18455085e9d9c0 Mon Sep 17 00:00:00 2001 From: Ed Tanous Date: Fri, 9 Feb 2024 13:50:26 -0800 Subject: Consolidate Vm implementations As much as the two vm implementations SEEM different, the differences largely lie in how we're getting the nbd proxy socket. One is relying on launching a process (nbd-proxy), the other is getting the fd from dbus. Given [1] exists and is in process, we need to have a plan for getting these two VM implementations into one, once that patchset is complete. This commit: Splits the vm-websocket option into vm-websocket-provider, providing two options, nbd-proxy, and virtual-media (the names of the respective apps). To accomplish this, it moves the contents of nbd-proxy into include/vm-websocket, so we can compare the similarities and start consolidating. The longer term intent is that the nbd-proxy option will be completely removed, and the code deleted. This has the additional advantage that we will no longer require the boost::process dependency, as all info will be available on dbus. As part of this, the nbd proxy websocket is also registered at /vm/0/0, to be backward compatible with the old interfaces. Tested: Code compiles. Need some help here. [1] https://gerrit.openbmc.org/c/openbmc/jsnbd/+/49944 Change-Id: Iedbca169ea40d45a8775f843792b874a248bb594 Signed-off-by: Ed Tanous --- config/bmcweb_config.h.in | 5 + config/meson.build | 3 + include/nbd_proxy.hpp | 387 ---------------------------------- include/vm_websocket.hpp | 473 ++++++++++++++++++++++++++++++++++++++---- meson.build | 1 - meson_options.txt | 5 +- redfish-core/lib/managers.hpp | 9 +- src/webserver_run.cpp | 1 - 8 files changed, 450 insertions(+), 434 deletions(-) delete mode 100644 include/nbd_proxy.hpp diff --git a/config/bmcweb_config.h.in b/config/bmcweb_config.h.in index a8ae29ef91..eb0c79c77a 100644 --- a/config/bmcweb_config.h.in +++ b/config/bmcweb_config.h.in @@ -2,6 +2,7 @@ #include #include +#include // clang-format off constexpr const bool bmcwebInsecureEnableQueryParams = @BMCWEB_INSECURE_ENABLE_QUERY_PARAMS@ == 1; @@ -21,4 +22,8 @@ constexpr const bool bmcwebEnableHTTP2 = @BMCWEB_ENABLE_HTTP2@ == 1; constexpr const bool bmcwebEnableTLS = @BMCWEB_ENABLE_TLS@ == 1; constexpr const bool bmcwebMTLSCommonNameParsingMeta = @BMCWEB_ENABLE_MTLS_COMMON_NAME_PARSING_META@ == 1; + +constexpr const bool bmcwebNbdProxy = @BMCWEB_VIRTUAL_MEDIA_NBD@ == 1; + +constexpr const bool bmcwebVmWebsocket = @BMCWEB_VIRTUAL_MEDIA_VM@ == 1; // clang-format on diff --git a/config/meson.build b/config/meson.build index 26c9bd4b3a..9533bd6b46 100644 --- a/config/meson.build +++ b/config/meson.build @@ -26,6 +26,9 @@ conf_data.set10( get_option('mutual-tls-common-name-parsing') == 'meta', ) +conf_data.set10('BMCWEB_VIRTUAL_MEDIA_VM', get_option('vm-websocket').allowed()) +conf_data.set10('BMCWEB_VIRTUAL_MEDIA_NBD', false) + # Logging level loglvlopt = get_option('bmcweb-logging') if get_option('buildtype').startswith('debug') and loglvlopt == 'disabled' diff --git a/include/nbd_proxy.hpp b/include/nbd_proxy.hpp deleted file mode 100644 index adc51a85ef..0000000000 --- a/include/nbd_proxy.hpp +++ /dev/null @@ -1,387 +0,0 @@ -/* -// Copyright (c) 2019 Intel Corporation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -*/ -#pragma once -#include "app.hpp" -#include "dbus_utility.hpp" -#include "privileges.hpp" -#include "websocket.hpp" - -#include -#include -#include - -#include - -namespace crow -{ - -namespace nbd_proxy -{ - -using boost::asio::local::stream_protocol; - -static constexpr size_t nbdBufferSize = 131088; -constexpr const char* requiredPrivilegeString = "ConfigureManager"; - -struct NbdProxyServer : std::enable_shared_from_this -{ - NbdProxyServer(crow::websocket::Connection& connIn, - const std::string& socketIdIn, - const std::string& endpointIdIn, const std::string& pathIn) : - socketId(socketIdIn), - endpointId(endpointIdIn), path(pathIn), - - peerSocket(connIn.getIoContext()), - acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)), - connection(connIn) - {} - - NbdProxyServer(const NbdProxyServer&) = delete; - NbdProxyServer(NbdProxyServer&&) = delete; - NbdProxyServer& operator=(const NbdProxyServer&) = delete; - NbdProxyServer& operator=(NbdProxyServer&&) = delete; - - ~NbdProxyServer() - { - BMCWEB_LOG_DEBUG("NbdProxyServer destructor"); - - BMCWEB_LOG_DEBUG("peerSocket->close()"); - boost::system::error_code ec; - peerSocket.close(ec); - - BMCWEB_LOG_DEBUG("std::filesystem::remove({})", socketId); - std::error_code ec2; - std::filesystem::remove(socketId.c_str(), ec2); - if (ec2) - { - BMCWEB_LOG_DEBUG("Failed to remove file, ignoring"); - } - - crow::connections::systemBus->async_method_call( - dbus::utility::logError, "xyz.openbmc_project.VirtualMedia", path, - "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); - } - - std::string getEndpointId() const - { - return endpointId; - } - - void run() - { - acceptor.async_accept( - [weak(weak_from_this())](const boost::system::error_code& ec, - stream_protocol::socket socket) { - if (ec) - { - BMCWEB_LOG_ERROR("UNIX socket: async_accept error = {}", - ec.message()); - return; - } - - BMCWEB_LOG_DEBUG("Connection opened"); - std::shared_ptr self = weak.lock(); - if (self == nullptr) - { - return; - } - - self->connection.resumeRead(); - self->peerSocket = std::move(socket); - // Start reading from socket - self->doRead(); - }); - - auto mountHandler = [weak(weak_from_this())]( - const boost::system::error_code& ec, bool) { - std::shared_ptr self = weak.lock(); - if (self == nullptr) - { - return; - } - if (ec) - { - BMCWEB_LOG_ERROR("DBus error: cannot call mount method = {}", - ec.message()); - - self->connection.close("Failed to mount media"); - return; - } - }; - - crow::connections::systemBus->async_method_call( - std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path, - "xyz.openbmc_project.VirtualMedia.Proxy", "Mount"); - } - - void send(std::string_view buffer, std::function&& onDone) - { - size_t copied = boost::asio::buffer_copy( - ws2uxBuf.prepare(buffer.size()), boost::asio::buffer(buffer)); - ws2uxBuf.commit(copied); - - doWrite(std::move(onDone)); - } - - private: - void doRead() - { - // Trigger async read - peerSocket.async_read_some( - ux2wsBuf.prepare(nbdBufferSize), - [weak(weak_from_this())](const boost::system::error_code& ec, - size_t bytesRead) { - if (ec) - { - BMCWEB_LOG_ERROR("UNIX socket: async_read_some error = {}", - ec.message()); - return; - } - std::shared_ptr self = weak.lock(); - if (self == nullptr) - { - return; - } - - // Send to websocket - self->ux2wsBuf.commit(bytesRead); - self->connection.sendEx( - crow::websocket::MessageType::Binary, - boost::beast::buffers_to_string(self->ux2wsBuf.data()), - [weak(self->weak_from_this())]() { - std::shared_ptr self2 = weak.lock(); - if (self2 != nullptr) - { - self2->ux2wsBuf.consume(self2->ux2wsBuf.size()); - self2->doRead(); - } - }); - }); - } - - void doWrite(std::function&& onDone) - { - if (uxWriteInProgress) - { - BMCWEB_LOG_ERROR("Write in progress"); - return; - } - - if (ws2uxBuf.size() == 0) - { - BMCWEB_LOG_ERROR("No data to write to UNIX socket"); - return; - } - - uxWriteInProgress = true; - peerSocket.async_write_some( - ws2uxBuf.data(), - [weak(weak_from_this()), - onDone(std::move(onDone))](const boost::system::error_code& ec, - size_t bytesWritten) mutable { - std::shared_ptr self = weak.lock(); - if (self == nullptr) - { - return; - } - - self->ws2uxBuf.consume(bytesWritten); - self->uxWriteInProgress = false; - - if (ec) - { - BMCWEB_LOG_ERROR("UNIX: async_write error = {}", ec.message()); - self->connection.close("Internal error"); - return; - } - - // Retrigger doWrite if there is something in buffer - if (self->ws2uxBuf.size() > 0) - { - self->doWrite(std::move(onDone)); - return; - } - onDone(); - }); - } - - // Keeps UNIX socket endpoint file path - const std::string socketId; - const std::string endpointId; - const std::string path; - - bool uxWriteInProgress = false; - - // UNIX => WebSocket buffer - boost::beast::flat_static_buffer ux2wsBuf; - - // WebSocket => UNIX buffer - boost::beast::flat_static_buffer ws2uxBuf; - - // The socket used to communicate with the client. - stream_protocol::socket peerSocket; - - // Default acceptor for UNIX socket - stream_protocol::acceptor acceptor; - - crow::websocket::Connection& connection; -}; - -using SessionMap = boost::container::flat_map>; -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -static SessionMap sessions; - -inline void - afterGetManagedObjects(crow::websocket::Connection& conn, - const boost::system::error_code& ec, - const dbus::utility::ManagedObjectType& objects) -{ - const std::string* socketValue = nullptr; - const std::string* endpointValue = nullptr; - const std::string* endpointObjectPath = nullptr; - - if (ec) - { - BMCWEB_LOG_ERROR("DBus error: {}", ec.message()); - conn.close("Failed to create mount point"); - return; - } - - for (const auto& [objectPath, interfaces] : objects) - { - for (const auto& [interface, properties] : interfaces) - { - if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint") - { - continue; - } - - for (const auto& [name, value] : properties) - { - if (name == "EndpointId") - { - endpointValue = std::get_if(&value); - - if (endpointValue == nullptr) - { - BMCWEB_LOG_ERROR("EndpointId property value is null"); - } - } - if (name == "Socket") - { - socketValue = std::get_if(&value); - if (socketValue == nullptr) - { - BMCWEB_LOG_ERROR("Socket property value is null"); - } - } - } - } - - if ((endpointValue != nullptr) && (socketValue != nullptr) && - *endpointValue == conn.url().path()) - { - endpointObjectPath = &objectPath.str; - break; - } - } - - if (objects.empty() || endpointObjectPath == nullptr) - { - BMCWEB_LOG_ERROR("Cannot find requested EndpointId"); - conn.close("Failed to match EndpointId"); - return; - } - - for (const auto& session : sessions) - { - if (session.second->getEndpointId() == conn.url().path()) - { - BMCWEB_LOG_ERROR("Cannot open new connection - socket is in use"); - conn.close("Slot is in use"); - return; - } - } - - // If the socket file exists (i.e. after bmcweb crash), - // we cannot reuse it. - std::remove((*socketValue).c_str()); - - sessions[&conn] = std::make_shared( - conn, *socketValue, *endpointValue, *endpointObjectPath); - - sessions[&conn]->run(); -}; -inline void onOpen(crow::websocket::Connection& conn) -{ - BMCWEB_LOG_DEBUG("nbd-proxy.onopen({})", logPtr(&conn)); - - sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia"); - dbus::utility::getManagedObjects( - "xyz.openbmc_project.VirtualMedia", path, - [&conn](const boost::system::error_code& ec, - const dbus::utility::ManagedObjectType& objects) { - afterGetManagedObjects(conn, ec, objects); - }); - - // We need to wait for dbus and the websockets to hook up before data is - // sent/received. Tell the core to hold off messages until the sockets are - // up - conn.deferRead(); -} - -inline void onClose(crow::websocket::Connection& conn, - const std::string& reason) -{ - BMCWEB_LOG_DEBUG("nbd-proxy.onclose(reason = '{}')", reason); - auto session = sessions.find(&conn); - if (session == sessions.end()) - { - BMCWEB_LOG_DEBUG("No session to close"); - return; - } - // Remove reference to session in global map - sessions.erase(session); -} - -inline void onMessage(crow::websocket::Connection& conn, std::string_view data, - crow::websocket::MessageType /*type*/, - std::function&& whenComplete) -{ - BMCWEB_LOG_DEBUG("nbd-proxy.onMessage(len = {})", data.size()); - - // Acquire proxy from sessions - auto session = sessions.find(&conn); - if (session == sessions.end() || session->second == nullptr) - { - whenComplete(); - return; - } - - session->second->send(data, std::move(whenComplete)); -} - -inline void requestRoutes(App& app) -{ - BMCWEB_ROUTE(app, "/nbd/") - .websocket() - .onopen(onOpen) - .onclose(onClose) - .onmessageex(onMessage); -} -} // namespace nbd_proxy -} // namespace crow diff --git a/include/vm_websocket.hpp b/include/vm_websocket.hpp index 3a72b3adb5..a516edd6f4 100644 --- a/include/vm_websocket.hpp +++ b/include/vm_websocket.hpp @@ -1,18 +1,26 @@ #pragma once #include "app.hpp" +#include "dbus_utility.hpp" +#include "privileges.hpp" #include "websocket.hpp" +#include #include #include +#include +#include #include +#include #include #include #include +#include namespace crow { + namespace obmc_vm { @@ -163,65 +171,452 @@ class Handler : public std::enable_shared_from_this // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static std::shared_ptr handler; -inline void requestRoutes(App& app) +} // namespace obmc_vm + +namespace nbd_proxy { - BMCWEB_ROUTE(app, "/vm/0/0") - .privileges({{"ConfigureComponents", "ConfigureManager"}}) - .websocket() - .onopen([](crow::websocket::Connection& conn) { - BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); +using boost::asio::local::stream_protocol; + +// The max network block device buffer size is 128kb plus 16bytes +// for the message header: +// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message +static constexpr auto nbdBufferSize = (128 * 1024 + 16) * 4; + +struct NbdProxyServer : std::enable_shared_from_this +{ + NbdProxyServer(crow::websocket::Connection& connIn, + const std::string& socketIdIn, + const std::string& endpointIdIn, const std::string& pathIn) : + socketId(socketIdIn), + endpointId(endpointIdIn), path(pathIn), - if (session != nullptr) + peerSocket(connIn.getIoContext()), + acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)), + connection(connIn) + {} + + NbdProxyServer(const NbdProxyServer&) = delete; + NbdProxyServer(NbdProxyServer&&) = delete; + NbdProxyServer& operator=(const NbdProxyServer&) = delete; + NbdProxyServer& operator=(NbdProxyServer&&) = delete; + + ~NbdProxyServer() + { + BMCWEB_LOG_DEBUG("NbdProxyServer destructor"); + + BMCWEB_LOG_DEBUG("peerSocket->close()"); + boost::system::error_code ec; + peerSocket.close(ec); + + BMCWEB_LOG_DEBUG("std::filesystem::remove({})", socketId); + std::error_code ec2; + std::filesystem::remove(socketId.c_str(), ec2); + if (ec2) { - conn.close("Session already connected"); - return; + BMCWEB_LOG_DEBUG("Failed to remove file, ignoring"); } - if (handler != nullptr) + crow::connections::systemBus->async_method_call( + dbus::utility::logError, "xyz.openbmc_project.VirtualMedia", path, + "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); + } + + std::string getEndpointId() const + { + return endpointId; + } + + void run() + { + acceptor.async_accept( + [weak(weak_from_this())](const boost::system::error_code& ec, + stream_protocol::socket socket) { + if (ec) + { + BMCWEB_LOG_ERROR("UNIX socket: async_accept error = {}", + ec.message()); + return; + } + + BMCWEB_LOG_DEBUG("Connection opened"); + std::shared_ptr self = weak.lock(); + if (self == nullptr) + { + return; + } + + self->connection.resumeRead(); + self->peerSocket = std::move(socket); + // Start reading from socket + self->doRead(); + }); + + auto mountHandler = [weak(weak_from_this())]( + const boost::system::error_code& ec, bool) { + std::shared_ptr self = weak.lock(); + if (self == nullptr) + { + return; + } + if (ec) + { + BMCWEB_LOG_ERROR("DBus error: cannot call mount method = {}", + ec.message()); + + self->connection.close("Failed to mount media"); + return; + } + }; + + crow::connections::systemBus->async_method_call( + std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path, + "xyz.openbmc_project.VirtualMedia.Proxy", "Mount"); + } + + void send(std::string_view buffer, std::function&& onDone) + { + size_t copied = boost::asio::buffer_copy( + ws2uxBuf.prepare(buffer.size()), boost::asio::buffer(buffer)); + ws2uxBuf.commit(copied); + + doWrite(std::move(onDone)); + } + + private: + void doRead() + { + // Trigger async read + peerSocket.async_read_some( + ux2wsBuf.prepare(nbdBufferSize), + [weak(weak_from_this())](const boost::system::error_code& ec, + size_t bytesRead) { + if (ec) + { + BMCWEB_LOG_ERROR("UNIX socket: async_read_some error = {}", + ec.message()); + return; + } + std::shared_ptr self = weak.lock(); + if (self == nullptr) + { + return; + } + + // Send to websocket + self->ux2wsBuf.commit(bytesRead); + self->connection.sendEx( + crow::websocket::MessageType::Binary, + boost::beast::buffers_to_string(self->ux2wsBuf.data()), + [weak(self->weak_from_this())]() { + std::shared_ptr self2 = weak.lock(); + if (self2 != nullptr) + { + self2->ux2wsBuf.consume(self2->ux2wsBuf.size()); + self2->doRead(); + } + }); + }); + } + + void doWrite(std::function&& onDone) + { + if (uxWriteInProgress) { - conn.close("Handler already running"); + BMCWEB_LOG_ERROR("Write in progress"); return; } - session = &conn; - - // media is the last digit of the endpoint /vm/0/0. A future - // enhancement can include supporting different endpoint values. - const char* media = "0"; - handler = std::make_shared(media, conn.getIoContext()); - handler->connect(); - }) - .onclose([](crow::websocket::Connection& conn, - const std::string& /*reason*/) { - if (&conn != session) + if (ws2uxBuf.size() == 0) { + BMCWEB_LOG_ERROR("No data to write to UNIX socket"); return; } - session = nullptr; - handler->doClose(); - handler->inputBuffer->clear(); - handler->outputBuffer->clear(); - handler.reset(); - }) - .onmessage([](crow::websocket::Connection& conn, - const std::string& data, bool) { - if (data.length() > - handler->inputBuffer->capacity() - handler->inputBuffer->size()) + uxWriteInProgress = true; + peerSocket.async_write_some( + ws2uxBuf.data(), + [weak(weak_from_this()), + onDone(std::move(onDone))](const boost::system::error_code& ec, + size_t bytesWritten) mutable { + std::shared_ptr self = weak.lock(); + if (self == nullptr) + { + return; + } + + self->ws2uxBuf.consume(bytesWritten); + self->uxWriteInProgress = false; + + if (ec) + { + BMCWEB_LOG_ERROR("UNIX: async_write error = {}", ec.message()); + self->connection.close("Internal error"); + return; + } + + // Retrigger doWrite if there is something in buffer + if (self->ws2uxBuf.size() > 0) + { + self->doWrite(std::move(onDone)); + return; + } + onDone(); + }); + } + + // Keeps UNIX socket endpoint file path + const std::string socketId; + const std::string endpointId; + const std::string path; + + bool uxWriteInProgress = false; + + // UNIX => WebSocket buffer + boost::beast::flat_static_buffer ux2wsBuf; + + // WebSocket => UNIX buffer + boost::beast::flat_static_buffer ws2uxBuf; + + // The socket used to communicate with the client. + stream_protocol::socket peerSocket; + + // Default acceptor for UNIX socket + stream_protocol::acceptor acceptor; + + crow::websocket::Connection& connection; +}; + +using SessionMap = boost::container::flat_map>; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +static SessionMap sessions; + +inline void + afterGetManagedObjects(crow::websocket::Connection& conn, + const boost::system::error_code& ec, + const dbus::utility::ManagedObjectType& objects) +{ + const std::string* socketValue = nullptr; + const std::string* endpointValue = nullptr; + const std::string* endpointObjectPath = nullptr; + + if (ec) + { + BMCWEB_LOG_ERROR("DBus error: {}", ec.message()); + conn.close("Failed to create mount point"); + return; + } + + for (const auto& [objectPath, interfaces] : objects) + { + for (const auto& [interface, properties] : interfaces) { - BMCWEB_LOG_ERROR("Buffer overrun when writing {} bytes", - data.length()); - conn.close("Buffer overrun"); + if (interface != "xyz.openbmc_project.VirtualMedia.MountPoint") + { + continue; + } + + for (const auto& [name, value] : properties) + { + if (name == "EndpointId") + { + endpointValue = std::get_if(&value); + + if (endpointValue == nullptr) + { + BMCWEB_LOG_ERROR("EndpointId property value is null"); + } + } + if (name == "Socket") + { + socketValue = std::get_if(&value); + if (socketValue == nullptr) + { + BMCWEB_LOG_ERROR("Socket property value is null"); + } + } + } + } + + if ((endpointValue != nullptr) && (socketValue != nullptr) && + *endpointValue == conn.url().path()) + { + endpointObjectPath = &objectPath.str; + break; + } + } + + if (objects.empty() || endpointObjectPath == nullptr) + { + BMCWEB_LOG_ERROR("Cannot find requested EndpointId"); + conn.close("Failed to match EndpointId"); + return; + } + + for (const auto& session : sessions) + { + if (session.second->getEndpointId() == conn.url().path()) + { + BMCWEB_LOG_ERROR("Cannot open new connection - socket is in use"); + conn.close("Slot is in use"); return; } + } + + // If the socket file exists (i.e. after bmcweb crash), + // we cannot reuse it. + std::remove((*socketValue).c_str()); + + sessions[&conn] = std::make_shared( + conn, *socketValue, *endpointValue, *endpointObjectPath); + + sessions[&conn]->run(); +}; + +inline void onOpen(crow::websocket::Connection& conn) +{ + BMCWEB_LOG_DEBUG("nbd-proxy.onopen({})", logPtr(&conn)); + + sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia"); + dbus::utility::getManagedObjects( + "xyz.openbmc_project.VirtualMedia", path, + [&conn](const boost::system::error_code& ec, + const dbus::utility::ManagedObjectType& objects) { + afterGetManagedObjects(conn, ec, objects); + }); + + // We need to wait for dbus and the websockets to hook up before data is + // sent/received. Tell the core to hold off messages until the sockets are + // up + conn.deferRead(); +} + +inline void onClose(crow::websocket::Connection& conn, + const std::string& reason) +{ + BMCWEB_LOG_DEBUG("nbd-proxy.onclose(reason = '{}')", reason); + auto session = sessions.find(&conn); + if (session == sessions.end()) + { + BMCWEB_LOG_DEBUG("No session to close"); + return; + } + // Remove reference to session in global map + sessions.erase(session); +} + +inline void onMessage(crow::websocket::Connection& conn, std::string_view data, + crow::websocket::MessageType /*type*/, + std::function&& whenComplete) +{ + BMCWEB_LOG_DEBUG("nbd-proxy.onMessage(len = {})", data.size()); + + // Acquire proxy from sessions + auto session = sessions.find(&conn); + if (session == sessions.end() || session->second == nullptr) + { + whenComplete(); + return; + } + + session->second->send(data, std::move(whenComplete)); +} + +inline void requestRoutes(App& app) +{ + BMCWEB_ROUTE(app, "/nbd/") + .websocket() + .onopen(onOpen) + .onclose(onClose) + .onmessageex(onMessage); +} +} // namespace nbd_proxy + +namespace obmc_vm +{ + +inline void requestRoutes(App& app) +{ + static_assert( + !(bmcwebVmWebsocket && bmcwebNbdProxy), + "nbd proxy cannot be turned on at the same time as vm websocket."); + + if constexpr (bmcwebVmWebsocket) + { + BMCWEB_ROUTE(app, "/nbd/") + .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .websocket() + .onopen(nbd_proxy::onOpen) + .onclose(nbd_proxy::onClose) + .onmessageex(nbd_proxy::onMessage); + + BMCWEB_ROUTE(app, "/vm/0/0") + .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .websocket() + .onopen(nbd_proxy::onOpen) + .onclose(nbd_proxy::onClose) + .onmessageex(nbd_proxy::onMessage); + } + if constexpr (bmcwebNbdProxy) + { + BMCWEB_ROUTE(app, "/vm/0/0") + .privileges({{"ConfigureComponents", "ConfigureManager"}}) + .websocket() + .onopen([](crow::websocket::Connection& conn) { + BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); + + if (session != nullptr) + { + conn.close("Session already connected"); + return; + } + + if (handler != nullptr) + { + conn.close("Handler already running"); + return; + } + + session = &conn; + + // media is the last digit of the endpoint /vm/0/0. A future + // enhancement can include supporting different endpoint values. + const char* media = "0"; + handler = std::make_shared(media, conn.getIoContext()); + handler->connect(); + }) + .onclose([](crow::websocket::Connection& conn, + const std::string& /*reason*/) { + if (&conn != session) + { + return; + } + + session = nullptr; + handler->doClose(); + handler->inputBuffer->clear(); + handler->outputBuffer->clear(); + handler.reset(); + }) + .onmessage([](crow::websocket::Connection& conn, + const std::string& data, bool) { + if (data.length() > + handler->inputBuffer->capacity() - handler->inputBuffer->size()) + { + BMCWEB_LOG_ERROR("Buffer overrun when writing {} bytes", + data.length()); + conn.close("Buffer overrun"); + return; + } - size_t copied = boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()), boost::asio::buffer(data)); - handler->inputBuffer->commit(copied); - handler->doWrite(); - }); + handler->inputBuffer->commit(data.size()); + handler->doWrite(); + }); + } } } // namespace obmc_vm + } // namespace crow diff --git a/meson.build b/meson.build index cabb65f9d8..5bb3fdfc18 100644 --- a/meson.build +++ b/meson.build @@ -89,7 +89,6 @@ feature_map = { 'session-auth': '-DBMCWEB_ENABLE_SESSION_AUTHENTICATION', 'static-hosting': '-DBMCWEB_ENABLE_STATIC_HOSTING', 'experimental-redfish-multi-computer-system': '-DBMCWEB_ENABLE_MULTI_COMPUTERSYSTEM', - 'vm-websocket': '-DBMCWEB_ENABLE_VM_WEBSOCKET', 'xtoken-auth': '-DBMCWEB_ENABLE_XTOKEN_AUTHENTICATION', #'vm-nbdproxy' : '-DBMCWEB_ENABLE_VM_NBDPROXY', } diff --git a/meson_options.txt b/meson_options.txt index d10d1b3dde..8a497ed8cc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -24,7 +24,7 @@ option( 'vm-websocket', type: 'feature', value: 'enabled', - description: '''Enable the Virtual Media WebSocket. Path is /vm/0/0 to + description: '''Enable the Virtual Media WebSocket. Path is /vm/0/0 and /nbd/ to open the websocket. See https://github.com/openbmc/jsnbd/blob/master/README.''' ) @@ -37,7 +37,8 @@ option( # opportunity to upstream their backend implementation #option( # 'vm-nbdproxy', -# type: 'feature', value: 'disabled', +# type: 'feature', +# value: 'disabled', # description: 'Enable the Virtual Media WebSocket.' #) diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp index 5b87cc54b3..592bbd8642 100644 --- a/redfish-core/lib/managers.hpp +++ b/redfish-core/lib/managers.hpp @@ -1945,10 +1945,11 @@ inline void requestRoutesManager(App& app) asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] = "/redfish/v1/Managers/bmc/EthernetInterfaces"; -#ifdef BMCWEB_ENABLE_VM_NBDPROXY - asyncResp->res.jsonValue["VirtualMedia"]["@odata.id"] = - "/redfish/v1/Managers/bmc/VirtualMedia"; -#endif // BMCWEB_ENABLE_VM_NBDPROXY + if constexpr (bmcwebNbdProxy) + { + asyncResp->res.jsonValue["VirtualMedia"]["@odata.id"] = + "/redfish/v1/Managers/bmc/VirtualMedia"; + } // default oem data nlohmann::json& oem = asyncResp->res.jsonValue["Oem"]; diff --git a/src/webserver_run.cpp b/src/webserver_run.cpp index f02ead96b9..81a78cc5b3 100644 --- a/src/webserver_run.cpp +++ b/src/webserver_run.cpp @@ -13,7 +13,6 @@ #include "kvm_websocket.hpp" #include "logging.hpp" #include "login_routes.hpp" -#include "nbd_proxy.hpp" #include "obmc_console.hpp" #include "openbmc_dbus_rest.hpp" #include "redfish.hpp" -- cgit v1.2.3