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>
diff --git a/config/bmcweb_config.h.in b/config/bmcweb_config.h.in
index a8ae29e..eb0c79c 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 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 26c9bd4..9533bd6 100644
--- a/config/meson.build
+++ b/config/meson.build
@@ -26,6 +26,9 @@
     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 adc51a8..0000000
--- 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 3a72b3a..a516edd 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 @@
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 static std::shared_ptr<Handler> handler;
 
+} // namespace obmc_vm
+
+namespace nbd_proxy
+{
+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),
+
+        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, "/vm/0/0")
-        .privileges({{"ConfigureComponents", "ConfigureManager"}})
+    BMCWEB_ROUTE(app, "/nbd/<str>")
         .websocket()
-        .onopen([](crow::websocket::Connection& conn) {
-        BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
+        .onopen(onOpen)
+        .onclose(onClose)
+        .onmessageex(onMessage);
+}
+} // namespace nbd_proxy
 
-        if (session != nullptr)
-        {
-            conn.close("Session already connected");
-            return;
-        }
+namespace obmc_vm
+{
 
-        if (handler != nullptr)
-        {
-            conn.close("Handler already running");
-            return;
-        }
+inline void requestRoutes(App& app)
+{
+    static_assert(
+        !(bmcwebVmWebsocket && bmcwebNbdProxy),
+        "nbd proxy cannot be turned on at the same time as vm websocket.");
 
-        session = &conn;
+    if constexpr (bmcwebVmWebsocket)
+    {
+        BMCWEB_ROUTE(app, "/nbd/<str>")
+            .privileges({{"ConfigureComponents", "ConfigureManager"}})
+            .websocket()
+            .onopen(nbd_proxy::onOpen)
+            .onclose(nbd_proxy::onClose)
+            .onmessageex(nbd_proxy::onMessage);
 
-        // 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;
-        }
+        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));
 
-        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;
-        }
+            if (session != nullptr)
+            {
+                conn.close("Session already connected");
+                return;
+            }
 
-        size_t copied =
+            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;
+            }
+
             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 cabb65f..5bb3fdf 100644
--- a/meson.build
+++ b/meson.build
@@ -89,7 +89,6 @@
     '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 d10d1b3..8a497ed 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -24,7 +24,7 @@
     '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 @@
 # 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 5b87cc5..592bbd8 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -1945,10 +1945,11 @@
         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 f02ead9..81a78cc 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"