summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEd Tanous <ed@tanous.net>2024-02-10 00:50:26 +0300
committerEd Tanous <ed@tanous.net>2024-04-30 02:12:43 +0300
commit36c0f2a35e670a4b798b7b42fd18455085e9d9c0 (patch)
tree103f5e30be4b2e8118e8bfc0e82caaa4f2bad9a5
parent95c6307a9b2c02f74b5f5c677d6983f996332ee6 (diff)
downloadbmcweb-36c0f2a35e670a4b798b7b42fd18455085e9d9c0.tar.xz
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 <ed@tanous.net>
-rw-r--r--config/bmcweb_config.h.in5
-rw-r--r--config/meson.build3
-rw-r--r--include/nbd_proxy.hpp387
-rw-r--r--include/vm_websocket.hpp473
-rw-r--r--meson.build1
-rw-r--r--meson_options.txt5
-rw-r--r--redfish-core/lib/managers.hpp9
-rw-r--r--src/webserver_run.cpp1
8 files changed, 450 insertions, 434 deletions
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 <cstdint>
#include <cstddef>
+#include <string_view>
// 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 <boost/asio/local/stream_protocol.hpp>
-#include <boost/beast/core/buffers_to_string.hpp>
-#include <boost/container/flat_map.hpp>
-
-#include <string_view>
-
-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>
-{
- 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<NbdProxyServer> 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<NbdProxyServer> 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<void()>&& 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<NbdProxyServer> 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<NbdProxyServer> self2 = weak.lock();
- if (self2 != nullptr)
- {
- self2->ux2wsBuf.consume(self2->ux2wsBuf.size());
- self2->doRead();
- }
- });
- });
- }
-
- void doWrite(std::function<void()>&& 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<NbdProxyServer> 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<nbdBufferSize> ux2wsBuf;
-
- // WebSocket => UNIX buffer
- boost::beast::flat_static_buffer<nbdBufferSize> 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<crow::websocket::Connection*,
- std::shared_ptr<NbdProxyServer>>;
-// 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<std::string>(&value);
-
- if (endpointValue == nullptr)
- {
- BMCWEB_LOG_ERROR("EndpointId property value is null");
- }
- }
- if (name == "Socket")
- {
- socketValue = std::get_if<std::string>(&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<NbdProxyServer>(
- 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<void()>&& 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/<str>")
- .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 <boost/asio/local/stream_protocol.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/writable_pipe.hpp>
+#include <boost/asio/write.hpp>
+#include <boost/beast/core/buffers_to_string.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
+#include <boost/container/flat_map.hpp>
#include <boost/process/v2/process.hpp>
#include <boost/process/v2/stdio.hpp>
#include <csignal>
+#include <string_view>
namespace crow
{
+
namespace obmc_vm
{
@@ -163,65 +171,452 @@ class Handler : public std::enable_shared_from_this<Handler>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::shared_ptr<Handler> 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>
+{
+ 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<NbdProxyServer> 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<NbdProxyServer> 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<void()>&& 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<NbdProxyServer> 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<NbdProxyServer> self2 = weak.lock();
+ if (self2 != nullptr)
+ {
+ self2->ux2wsBuf.consume(self2->ux2wsBuf.size());
+ self2->doRead();
+ }
+ });
+ });
+ }
+
+ void doWrite(std::function<void()>&& 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<Handler>(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<NbdProxyServer> 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<nbdBufferSize> ux2wsBuf;
+
+ // WebSocket => UNIX buffer
+ boost::beast::flat_static_buffer<nbdBufferSize> 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<crow::websocket::Connection*,
+ std::shared_ptr<NbdProxyServer>>;
+// 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<std::string>(&value);
+
+ if (endpointValue == nullptr)
+ {
+ BMCWEB_LOG_ERROR("EndpointId property value is null");
+ }
+ }
+ if (name == "Socket")
+ {
+ socketValue = std::get_if<std::string>(&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<NbdProxyServer>(
+ 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<void()>&& 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/<str>")
+ .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/<str>")
+ .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<Handler>(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/<id> 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"