Enable HTTP additional sockets

This commit attempts to add the concept of an SSL detector from beast,
and add the capability into bmcweb.  This allows directing multiple
socket files to the bmcweb instance, and bmcweb will automatically sort
out whether or not they're SSL, and give the correct response.  This
allows users to plug in erroneous urls like "https://mybmc:80" and they
will forward and work correctly.

Some key design points:
The HTTP side of bmcweb implements the exact same http headers as the
HTTPS side, with the exception of HSTS, which is explicitly disallowed.
This is for consistency and security.

The above allows bmcweb builds to "select" the appropriate security
posture (http, https, or both) for a given channel using the
FileDescriptorName field within a socket file.  Items ending in:
both: Will support both HTTPS and HTTP redirect to HTTPS
https: Will support HTTPS only
http: will support HTTP only

Given the flexibility in bind statements, this allows administrators to
support essentially any security posture they like.  The openbmc
defaults are:
HTTPS + Redirect on both ports 443 and port 80 if http-redirect is
enabled

And HTTPS only if http-redirect is disabled.

This commit adds the following meson options that each take an array of
strings, indexex on the port.
additional-ports
  Adds additional ports that bmcweb should listen to.  This is always
  required when adding new ports.

additional-protocol
  Specifies 'http', 'https', or 'both' for whether or not tls is enfoced
  on this socket.  'both' allows bmcweb to detect whether a user has
  specified tls or not on a given connection and give the correct
  response.

additional-bind-to-device
  Accepts values that fill the SO_BINDTODEVICE flag in systemd/linux,
  and allows binding to a specific device

additional-auth
  Accepts values of 'auth' or 'noauth' that determines whether this
  socket should apply the normal authentication routines, or treat the
  socket as unauthenticated.

Tested:
Previous commits ran the below tests.
Ran the server with options enabled.  Tried:
```
curl -vvvv --insecure --user root:0penBmc http://192.168.7.2/redfish/v1/Managers/bmc
*   Trying 192.168.7.2:80...
* Connected to 192.168.7.2 (192.168.7.2) port 80 (#0)
* Server auth using Basic with user 'root'
> GET /redfish/v1/Managers/bmc HTTP/1.1
> Host: 192.168.7.2
> Authorization: Basic cm9vdDowcGVuQm1j
> User-Agent: curl/7.72.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Location: https://192.168.7.2
< X-Frame-Options: DENY
< Pragma: no-cache
< Cache-Control: no-Store,no-Cache
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< Content-Security-Policy: default-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self' wss:
< Date: Fri, 08 Jan 2021 01:43:49 GMT
< Connection: close
< Content-Length: 0
<
* Closing connection 0
```

Observe above:
webserver returned 301 redirect.
webserver returned the appropriate security headers
webserver immediately closed the connection.

The same test above over https:// returns the values as expected

Loaded the webui to test static file hosting.  Webui logs in and works
as expected.

Used the scripts/websocket_test.py to verify that websockets work.
Sensors report as expected.

Change-Id: Ib5733bbe5473fed6e0e27c56cdead0bffedf2993
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/http/app.hpp b/http/app.hpp
index f8d4d97..8687c9c 100644
--- a/http/app.hpp
+++ b/http/app.hpp
@@ -2,9 +2,8 @@
 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
 #pragma once
 
-#include "bmcweb_config.h"
-
 #include "async_resp.hpp"
+#include "http_connect_types.hpp"
 #include "http_request.hpp"
 #include "http_server.hpp"
 #include "io_context_singleton.hpp"
@@ -15,16 +14,15 @@
 #include <sys/socket.h>
 #include <systemd/sd-daemon.h>
 
-#include <boost/asio/ip/address.hpp>
 #include <boost/asio/ip/tcp.hpp>
 #include <boost/asio/ssl/context.hpp>
-#include <boost/asio/ssl/stream.hpp>
 
+#include <cstddef>
 #include <cstdint>
 #include <memory>
 #include <optional>
+#include <span>
 #include <string>
-#include <type_traits>
 #include <utility>
 #include <vector>
 
@@ -37,12 +35,8 @@
 class App
 {
   public:
-    using ssl_socket_t = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;
     using raw_socket_t = boost::asio::ip::tcp::socket;
-
-    using socket_type = std::conditional_t<BMCWEB_INSECURE_DISABLE_SSL,
-                                           raw_socket_t, ssl_socket_t>;
-    using server_type = Server<App, socket_type>;
+    using server_type = Server<App, raw_socket_t>;
 
     template <typename Adaptor>
     void handleUpgrade(const std::shared_ptr<Request>& req,
@@ -84,43 +78,81 @@
         server->loadCertificate();
     }
 
-    static std::optional<boost::asio::ip::tcp::acceptor> setupSocket()
+    static HttpType getHttpType(std::string_view socketTypeString)
     {
-        constexpr int defaultPort = 18080;
-        if (sd_listen_fds(0) == 1)
+        if (socketTypeString == "http")
         {
-            BMCWEB_LOG_INFO("attempting systemd socket activation");
-            if (sd_is_socket_inet(SD_LISTEN_FDS_START, AF_UNSPEC, SOCK_STREAM,
-                                  1, 0) != 0)
+            BMCWEB_LOG_DEBUG("Got http socket");
+            return HttpType::HTTP;
+        }
+        if (socketTypeString == "https")
+        {
+            BMCWEB_LOG_DEBUG("Got https socket");
+            return HttpType::HTTPS;
+        }
+        if (socketTypeString == "both")
+        {
+            BMCWEB_LOG_DEBUG("Got hybrid socket");
+            return HttpType::BOTH;
+        }
+
+        // all other types https
+        BMCWEB_LOG_ERROR("Unknown http type={} assuming HTTPS only",
+                         socketTypeString);
+        return HttpType::HTTPS;
+    }
+
+    static std::vector<Acceptor> setupSocket()
+    {
+        std::vector<Acceptor> acceptors;
+        char** names = nullptr;
+        int listenFdCount = sd_listen_fds_with_names(0, &names);
+        BMCWEB_LOG_DEBUG("Got {} sockets to open", listenFdCount);
+
+        if (listenFdCount < 0)
+        {
+            BMCWEB_LOG_CRITICAL("Failed to read socket files");
+            return acceptors;
+        }
+        int socketIndex = 0;
+        for (char* name :
+             std::span<char*>(names, static_cast<size_t>(listenFdCount)))
+        {
+            if (name == nullptr)
+            {
+                continue;
+            }
+            // name looks like bmcweb_443_https_auth
+            // Assume HTTPS as default
+            std::string socketName(name);
+
+            std::vector<std::string> socknameComponents;
+            bmcweb::split(socknameComponents, socketName, '_');
+            HttpType httpType = getHttpType(socknameComponents[2]);
+
+            int listenFd = socketIndex + SD_LISTEN_FDS_START;
+            if (sd_is_socket_inet(listenFd, AF_UNSPEC, SOCK_STREAM, 1, 0) > 0)
             {
                 BMCWEB_LOG_INFO("Starting webserver on socket handle {}",
-                                SD_LISTEN_FDS_START);
-                return boost::asio::ip::tcp::acceptor(
-                    getIoContext(), boost::asio::ip::tcp::v6(),
-                    SD_LISTEN_FDS_START);
+                                listenFd);
+                acceptors.emplace_back(Acceptor{
+                    boost::asio::ip::tcp::acceptor(
+                        getIoContext(), boost::asio::ip::tcp::v6(), listenFd),
+                    httpType});
             }
-            BMCWEB_LOG_ERROR(
-                "bad incoming socket, starting webserver on port {}",
-                defaultPort);
+            socketIndex++;
         }
-        BMCWEB_LOG_INFO("Starting webserver on port {}", defaultPort);
-        return boost::asio::ip::tcp::acceptor(
-            getIoContext(),
-            boost::asio::ip::tcp::endpoint(
-                boost::asio::ip::make_address("0.0.0.0"), defaultPort));
+
+        return acceptors;
     }
 
     void run()
     {
         validate();
 
-        std::optional<boost::asio::ip::tcp::acceptor> acceptor = setupSocket();
-        if (!acceptor)
-        {
-            BMCWEB_LOG_CRITICAL("Couldn't start server");
-            return;
-        }
-        server.emplace(this, std::move(*acceptor), sslContext, getIoContext());
+        std::vector<Acceptor> acceptors = setupSocket();
+
+        server.emplace(this, std::move(acceptors));
         server->run();
     }
 
@@ -140,16 +172,6 @@
         return router.getRoutes(parent);
     }
 
-    App& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx)
-    {
-        sslContext = std::move(ctx);
-        BMCWEB_LOG_INFO("app::ssl context use_count={}",
-                        sslContext.use_count());
-        return *this;
-    }
-
-    std::shared_ptr<boost::asio::ssl::context> sslContext = nullptr;
-
     std::optional<server_type> server;
 
     Router router;
diff --git a/http/http2_connection.hpp b/http/http2_connection.hpp
index e982fb2..5399bd4 100644
--- a/http/http2_connection.hpp
+++ b/http/http2_connection.hpp
@@ -19,7 +19,6 @@
 #include <unistd.h>
 
 #include <boost/asio/buffer.hpp>
-#include <boost/asio/ip/tcp.hpp>
 #include <boost/asio/ssl/stream.hpp>
 #include <boost/beast/core/error.hpp>
 #include <boost/beast/http/field.hpp>
@@ -40,12 +39,21 @@
 #include <span>
 #include <string>
 #include <string_view>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
 namespace crow
 {
 
+template <typename>
+struct IsTls : std::false_type
+{};
+
+template <typename T>
+struct IsTls<boost::asio::ssl::stream<T>> : std::true_type
+{};
+
 struct Http2StreamData
 {
     std::shared_ptr<Request> req = std::make_shared<Request>();
@@ -575,9 +583,7 @@
 
     void close()
     {
-        if constexpr (std::is_same_v<Adaptor,
-                                     boost::asio::ssl::stream<
-                                         boost::asio::ip::tcp::socket>>)
+        if constexpr (IsTls<Adaptor>::value)
         {
             adaptor.next_layer().close();
         }
diff --git a/http/http_connect_types.hpp b/http/http_connect_types.hpp
new file mode 100644
index 0000000..da9d576
--- /dev/null
+++ b/http/http_connect_types.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace crow
+{
+enum class HttpType
+{
+    HTTPS, // Socket supports HTTPS only
+    HTTP,  // Socket supports HTTP only
+    BOTH   // Socket supports both HTTPS and HTTP, with HTTP Redirect
+};
+}
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index a6773bc..c9eb2ae 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -9,6 +9,7 @@
 #include "forward_unauthorized.hpp"
 #include "http2_connection.hpp"
 #include "http_body.hpp"
+#include "http_connect_types.hpp"
 #include "http_request.hpp"
 #include "http_response.hpp"
 #include "http_utility.hpp"
@@ -26,6 +27,8 @@
 #include <boost/asio/steady_timer.hpp>
 #include <boost/beast/_experimental/test/stream.hpp>
 #include <boost/beast/core/buffers_generator.hpp>
+#include <boost/beast/core/detect_ssl.hpp>
+#include <boost/beast/core/error.hpp>
 #include <boost/beast/core/flat_static_buffer.hpp>
 #include <boost/beast/http/error.hpp>
 #include <boost/beast/http/field.hpp>
@@ -64,14 +67,6 @@
 
 constexpr uint32_t httpHeaderLimit = 8192U;
 
-template <typename>
-struct IsTls : std::false_type
-{};
-
-template <typename T>
-struct IsTls<boost::asio::ssl::stream<T>> : std::true_type
-{};
-
 template <typename Adaptor, typename Handler>
 class Connection :
     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
@@ -79,10 +74,11 @@
     using self_type = Connection<Adaptor, Handler>;
 
   public:
-    Connection(Handler* handlerIn, boost::asio::steady_timer&& timerIn,
+    Connection(Handler* handlerIn, HttpType httpTypeIn,
+               boost::asio::steady_timer&& timerIn,
                std::function<std::string()>& getCachedDateStrF,
-               Adaptor&& adaptorIn) :
-        adaptor(std::move(adaptorIn)), handler(handlerIn),
+               boost::asio::ssl::stream<Adaptor>&& adaptorIn) :
+        httpType(httpTypeIn), adaptor(std::move(adaptorIn)), handler(handlerIn),
         timer(std::move(timerIn)), getCachedDateStr(getCachedDateStrF)
     {
         initParser();
@@ -139,38 +135,75 @@
 
     bool prepareMutualTls()
     {
-        if constexpr (IsTls<Adaptor>::value)
+        BMCWEB_LOG_DEBUG("prepareMutualTls");
+
+        constexpr std::string_view id = "bmcweb";
+
+        const char* idPtr = id.data();
+        const auto* idCPtr = std::bit_cast<const unsigned char*>(idPtr);
+        auto idLen = static_cast<unsigned int>(id.length());
+        int ret =
+            SSL_set_session_id_context(adaptor.native_handle(), idCPtr, idLen);
+        if (ret == 0)
         {
-            BMCWEB_LOG_DEBUG("prepareMutualTls");
+            BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
+            return false;
+        }
 
-            constexpr std::string_view id = "bmcweb";
+        BMCWEB_LOG_DEBUG("set_verify_callback");
 
-            const char* idPtr = id.data();
-            const auto* idCPtr = std::bit_cast<const unsigned char*>(idPtr);
-            auto idLen = static_cast<unsigned int>(id.length());
-            int ret = SSL_set_session_id_context(adaptor.native_handle(),
-                                                 idCPtr, idLen);
-            if (ret == 0)
-            {
-                BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
-                return false;
-            }
-
-            BMCWEB_LOG_DEBUG("set_verify_callback");
-
-            boost::system::error_code ec;
-            adaptor.set_verify_callback(
-                std::bind_front(&self_type::tlsVerifyCallback, this), ec);
-            if (ec)
-            {
-                BMCWEB_LOG_ERROR("Failed to set verify callback {}", ec);
-                return false;
-            }
+        boost::system::error_code ec;
+        adaptor.set_verify_callback(
+            std::bind_front(&self_type::tlsVerifyCallback, this), ec);
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("Failed to set verify callback {}", ec);
+            return false;
         }
 
         return true;
     }
 
+    void afterDetectSsl(const std::shared_ptr<self_type>& /*self*/,
+                        boost::beast::error_code ec, bool isTls)
+    {
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("Couldn't detect ssl ", ec);
+            return;
+        }
+        BMCWEB_LOG_DEBUG("{} TLS was detected as {}", logPtr(this), isTls);
+        if (isTls)
+        {
+            if (httpType != HttpType::HTTPS && httpType != HttpType::BOTH)
+            {
+                BMCWEB_LOG_WARNING(
+                    "{} Connection closed due to incompatible type",
+                    logPtr(this));
+                return;
+            }
+            httpType = HttpType::HTTPS;
+            adaptor.async_handshake(
+                boost::asio::ssl::stream_base::server, buffer.data(),
+                std::bind_front(&self_type::afterSslHandshake, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            if (httpType != HttpType::HTTP && httpType != HttpType::BOTH)
+            {
+                BMCWEB_LOG_WARNING(
+                    "{} Connection closed due to incompatible type",
+                    logPtr(this));
+                return;
+            }
+
+            httpType = HttpType::HTTP;
+            BMCWEB_LOG_INFO("Starting non-SSL session");
+            doReadHeaders();
+        }
+    }
+
     void start()
     {
         BMCWEB_LOG_DEBUG("{} Connection started, total {}", logPtr(this),
@@ -194,29 +227,23 @@
         startDeadline();
 
         readClientIp();
-
-        // TODO(ed) Abstract this to a more clever class with the idea of an
-        // asynchronous "start"
-        if constexpr (IsTls<Adaptor>::value)
-        {
-            adaptor.async_handshake(boost::asio::ssl::stream_base::server,
-                                    [this, self(shared_from_this())](
-                                        const boost::system::error_code& ec) {
-                                        if (ec)
-                                        {
-                                            return;
-                                        }
-                                        afterSslHandshake();
-                                    });
-        }
-        else
-        {
-            doReadHeaders();
-        }
+        boost::beast::async_detect_ssl(
+            adaptor.next_layer(), buffer,
+            std::bind_front(&self_type::afterDetectSsl, this,
+                            shared_from_this()));
     }
 
-    void afterSslHandshake()
+    void afterSslHandshake(const std::shared_ptr<self_type>& /*self*/,
+                           const boost::system::error_code& ec,
+                           size_t bytesParsed)
     {
+        buffer.consume(bytesParsed);
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("{} SSL handshake failed", logPtr(this));
+            return;
+        }
+        BMCWEB_LOG_DEBUG("{} SSL handshake succeeded", logPtr(this));
         // If http2 is enabled, negotiate the protocol
         if constexpr (BMCWEB_EXPERIMENTAL_HTTP2)
         {
@@ -231,10 +258,7 @@
                                  selectedProtocol, alpnlen);
                 if (selectedProtocol == "h2")
                 {
-                    auto http2 =
-                        std::make_shared<HTTP2Connection<Adaptor, Handler>>(
-                            std::move(adaptor), handler, getCachedDateStr);
-                    http2->start();
+                    upgradeToHttp2();
                     return;
                 }
             }
@@ -256,6 +280,39 @@
         instance.body_limit(boost::none);
     }
 
+    void upgradeToHttp2()
+    {
+        // TODO HTTP2Connection needs adaptor moved to a similar abstraction as
+        // HTTPConnection
+        if (httpType == HttpType::HTTP)
+        {
+            auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
+                std::move(adaptor.next_layer()), handler, getCachedDateStr);
+            if (http2settings.empty())
+            {
+                http2->start();
+            }
+            else
+            {
+                http2->startFromSettings(http2settings);
+            }
+        }
+        else
+        {
+            auto http2 = std::make_shared<
+                HTTP2Connection<boost::asio::ssl::stream<Adaptor>, Handler>>(
+                std::move(adaptor), handler, getCachedDateStr);
+            if (http2settings.empty())
+            {
+                http2->start();
+            }
+            else
+            {
+                http2->startFromSettings(http2settings);
+            }
+        }
+    }
+
     // returns whether connection was upgraded
     bool doUpgrade(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
     {
@@ -312,7 +369,16 @@
                         }
                     });
                 BMCWEB_LOG_INFO("{} Upgrading socket", logPtr(this));
-                handler->handleUpgrade(req, asyncResp, std::move(adaptor));
+                if (httpType == HttpType::HTTP)
+                {
+                    handler->handleUpgrade(req, asyncResp,
+                                           std::move(adaptor.next_layer()));
+                }
+                else
+                {
+                    handler->handleUpgrade(req, asyncResp, std::move(adaptor));
+                }
+
                 return true;
             }
         }
@@ -364,24 +430,23 @@
             return;
         }
         keepAlive = req->keepAlive();
-        if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
+
+        if (authenticationEnabled)
         {
-            if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
+            if (!crow::authentication::isOnAllowlist(req->url().path(),
+                                                     req->method()) &&
+                req->session == nullptr)
             {
-                if (!crow::authentication::isOnAllowlist(req->url().path(),
-                                                         req->method()) &&
-                    req->session == nullptr)
-                {
-                    BMCWEB_LOG_WARNING("Authentication failed");
-                    forward_unauthorized::sendUnauthorized(
-                        req->url().encoded_path(),
-                        req->getHeaderValue("X-Requested-With"),
-                        req->getHeaderValue("Accept"), res);
-                    completeRequest(res);
-                    return;
-                }
+                BMCWEB_LOG_WARNING("Authentication failed");
+                forward_unauthorized::sendUnauthorized(
+                    req->url().encoded_path(),
+                    req->getHeaderValue("X-Requested-With"),
+                    req->getHeaderValue("Accept"), res);
+                completeRequest(res);
+                return;
             }
         }
+
         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
         BMCWEB_LOG_DEBUG("Setting completion handler");
         asyncResp->res.setCompleteRequestHandler(
@@ -403,15 +468,8 @@
 
     void hardClose()
     {
-        if (mtlsSession != nullptr)
-        {
-            BMCWEB_LOG_DEBUG("{} Removing TLS session: {}", logPtr(this),
-                             mtlsSession->uniqueId);
-            persistent_data::SessionStore::getInstance().removeSession(
-                mtlsSession);
-        }
         BMCWEB_LOG_DEBUG("{} Closing socket", logPtr(this));
-        boost::beast::get_lowest_layer(adaptor).close();
+        adaptor.next_layer().close();
     }
 
     void tlsShutdownComplete(const std::shared_ptr<self_type>& self,
@@ -429,8 +487,16 @@
     {
         BMCWEB_LOG_DEBUG("{} Socket close requested", logPtr(this));
 
-        if constexpr (IsTls<Adaptor>::value)
+        if (httpType == HttpType::HTTPS)
         {
+            if (mtlsSession != nullptr)
+            {
+                BMCWEB_LOG_DEBUG("{} Removing TLS session: {}", logPtr(this),
+                                 mtlsSession->uniqueId);
+                persistent_data::SessionStore::getInstance().removeSession(
+                    mtlsSession);
+            }
+
             adaptor.async_shutdown(std::bind_front(
                 &self_type::tlsShutdownComplete, this, shared_from_this()));
         }
@@ -459,21 +525,23 @@
     {
         boost::system::error_code ec;
 
-        if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
-        {
-            boost::asio::ip::tcp::endpoint endpoint =
-                boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
+        boost::asio::ip::tcp::endpoint endpoint =
+            boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
 
-            if (ec)
-            {
-                // If remote endpoint fails keep going. "ClientOriginIPAddress"
-                // will be empty.
-                BMCWEB_LOG_ERROR(
-                    "Failed to get the client's IP Address. ec : {}", ec);
-                return;
-            }
-            ip = endpoint.address();
+        if (ec)
+        {
+            // If remote endpoint fails keep going. "ClientOriginIPAddress"
+            // will be empty.
+            BMCWEB_LOG_ERROR("Failed to get the client's IP Address. ec : {}",
+                             ec);
+            return;
         }
+        ip = endpoint.address();
+    }
+
+    void disableAuth()
+    {
+        authenticationEnabled = false;
     }
 
   private:
@@ -585,13 +653,10 @@
             return;
         }
 
-        constexpr bool isTest =
-            std::is_same_v<Adaptor, boost::beast::test::stream>;
-
-        if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH && !isTest)
+        if (authenticationEnabled)
         {
             boost::beast::http::verb method = parser->get().method();
-            userSession = crow::authentication::authenticate(
+            userSession = authentication::authenticate(
                 ip, res, method, parser->get().base(), mtlsSession);
         }
 
@@ -628,11 +693,21 @@
             BMCWEB_LOG_CRITICAL("Parser was not initialized.");
             return;
         }
-        // Clean up any previous Connection.
-        boost::beast::http::async_read_header(
-            adaptor, buffer, *parser,
-            std::bind_front(&self_type::afterReadHeaders, this,
-                            shared_from_this()));
+
+        if (httpType == HttpType::HTTP)
+        {
+            boost::beast::http::async_read_header(
+                adaptor.next_layer(), buffer, *parser,
+                std::bind_front(&self_type::afterReadHeaders, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            boost::beast::http::async_read_header(
+                adaptor, buffer, *parser,
+                std::bind_front(&self_type::afterReadHeaders, this,
+                                shared_from_this()));
+        }
     }
 
     void afterRead(const std::shared_ptr<self_type>& /*self*/,
@@ -697,9 +772,20 @@
             return;
         }
         startDeadline();
-        boost::beast::http::async_read_some(
-            adaptor, buffer, *parser,
-            std::bind_front(&self_type::afterRead, this, shared_from_this()));
+        if (httpType == HttpType::HTTP)
+        {
+            boost::beast::http::async_read_some(
+                adaptor.next_layer(), buffer, *parser,
+                std::bind_front(&self_type::afterRead, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            boost::beast::http::async_read_some(
+                adaptor, buffer, *parser,
+                std::bind_front(&self_type::afterRead, this,
+                                shared_from_this()));
+        }
     }
 
     void afterDoWrite(const std::shared_ptr<self_type>& /*self*/,
@@ -725,9 +811,7 @@
 
         if (res.result() == boost::beast::http::status::switching_protocols)
         {
-            auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
-                std::move(adaptor), handler, getCachedDateStr);
-            http2->startFromSettings(http2settings);
+            upgradeToHttp2();
             return;
         }
 
@@ -764,11 +848,22 @@
         res.preparePayload();
 
         startDeadline();
-        boost::beast::async_write(
-            adaptor,
-            boost::beast::http::message_generator(std::move(res.response)),
-            std::bind_front(&self_type::afterDoWrite, this,
-                            shared_from_this()));
+        if (httpType == HttpType::HTTP)
+        {
+            boost::beast::async_write(
+                adaptor.next_layer(),
+                boost::beast::http::message_generator(std::move(res.response)),
+                std::bind_front(&self_type::afterDoWrite, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            boost::beast::async_write(
+                adaptor,
+                boost::beast::http::message_generator(std::move(res.response)),
+                std::bind_front(&self_type::afterDoWrite, this,
+                                shared_from_this()));
+        }
     }
 
     void cancelDeadlineTimer()
@@ -835,7 +930,10 @@
         BMCWEB_LOG_DEBUG("{} timer started", logPtr(this));
     }
 
-    Adaptor adaptor;
+    bool authenticationEnabled = !BMCWEB_INSECURE_DISABLE_AUTH;
+    HttpType httpType = HttpType::BOTH;
+
+    boost::asio::ssl::stream<Adaptor> adaptor;
     Handler* handler;
 
     boost::asio::ip::address ip;
diff --git a/http/http_server.hpp b/http/http_server.hpp
index a7c2459..b2721c6 100644
--- a/http/http_server.hpp
+++ b/http/http_server.hpp
@@ -4,18 +4,19 @@
 
 #include "bmcweb_config.h"
 
+#include "http_connect_types.hpp"
 #include "http_connection.hpp"
+#include "io_context_singleton.hpp"
 #include "logging.hpp"
 #include "ssl_key_handler.hpp"
 
-#include <boost/asio/io_context.hpp>
 #include <boost/asio/ip/address.hpp>
 #include <boost/asio/ip/tcp.hpp>
 #include <boost/asio/signal_set.hpp>
 #include <boost/asio/ssl/context.hpp>
 #include <boost/asio/ssl/stream.hpp>
+#include <boost/asio/ssl/verify_mode.hpp>
 #include <boost/asio/steady_timer.hpp>
-#include <boost/beast/core/stream_traits.hpp>
 
 #include <chrono>
 #include <csignal>
@@ -24,25 +25,29 @@
 #include <functional>
 #include <memory>
 #include <string>
-#include <type_traits>
 #include <utility>
+#include <vector>
 
 namespace crow
 {
 
+struct Acceptor
+{
+    boost::asio::ip::tcp::acceptor acceptor;
+    HttpType httpType;
+};
+
 template <typename Handler, typename Adaptor = boost::asio::ip::tcp::socket>
 class Server
 {
     using self_t = Server<Handler, Adaptor>;
 
   public:
-    Server(Handler* handlerIn, boost::asio::ip::tcp::acceptor&& acceptorIn,
-           std::shared_ptr<boost::asio::ssl::context> adaptorCtxIn,
-           boost::asio::io_context& io) :
-        ioService(io), acceptor(std::move(acceptorIn)),
+    Server(Handler* handlerIn, std::vector<Acceptor>&& acceptorsIn) :
+        acceptors(std::move(acceptorsIn)),
+
         // NOLINTNEXTLINE(misc-include-cleaner)
-        signals(ioService, SIGINT, SIGTERM, SIGHUP), handler(handlerIn),
-        adaptorCtx(std::move(adaptorCtxIn))
+        signals(getIoContext(), SIGINT, SIGTERM, SIGHUP), handler(handlerIn)
     {}
 
     void updateDateStr()
@@ -75,8 +80,12 @@
             return dateStr;
         };
 
-        BMCWEB_LOG_INFO("bmcweb server is running, local endpoint {}",
-                        acceptor.local_endpoint().address().to_string());
+        for (const Acceptor& accept : acceptors)
+        {
+            BMCWEB_LOG_INFO(
+                "bmcweb server is running, local endpoint {}",
+                accept.acceptor.local_endpoint().address().to_string());
+        }
         startAsyncWaitForSignal();
         doAccept();
     }
@@ -88,10 +97,7 @@
             return;
         }
 
-        auto sslContext = ensuressl::getSslServerContext();
-
-        adaptorCtx = sslContext;
-        handler->ssl(std::move(sslContext));
+        adaptorCtx = ensuressl::getSslServerContext();
     }
 
     void startAsyncWaitForSignal()
@@ -112,20 +118,16 @@
                     }
                     else
                     {
-                        stop();
+                        getIoContext().stop();
                     }
                 }
             });
     }
 
-    void stop()
-    {
-        ioService.stop();
-    }
-    using Socket = boost::beast::lowest_layer_type<Adaptor>;
-    using SocketPtr = std::unique_ptr<Socket>;
+    using SocketPtr = std::unique_ptr<Adaptor>;
 
-    void afterAccept(SocketPtr socket, const boost::system::error_code& ec)
+    void afterAccept(SocketPtr socket, HttpType httpType,
+                     const boost::system::error_code& ec)
     {
         if (ec)
         {
@@ -133,51 +135,44 @@
             return;
         }
 
-        boost::asio::steady_timer timer(ioService);
-        std::shared_ptr<Connection<Adaptor, Handler>> connection;
-
-        if constexpr (std::is_same<Adaptor,
-                                   boost::asio::ssl::stream<
-                                       boost::asio::ip::tcp::socket>>::value)
+        boost::asio::steady_timer timer(getIoContext());
+        if (adaptorCtx == nullptr)
         {
-            if (adaptorCtx == nullptr)
-            {
-                BMCWEB_LOG_CRITICAL(
-                    "Asked to launch TLS socket but no context available");
-                return;
-            }
-            connection = std::make_shared<Connection<Adaptor, Handler>>(
-                handler, std::move(timer), getCachedDateStr,
-                Adaptor(std::move(*socket), *adaptorCtx));
-        }
-        else
-        {
-            connection = std::make_shared<Connection<Adaptor, Handler>>(
-                handler, std::move(timer), getCachedDateStr,
-                Adaptor(std::move(*socket)));
+            adaptorCtx = std::make_shared<boost::asio::ssl::context>(
+                boost::asio::ssl::context::tls_server);
         }
 
-        boost::asio::post(ioService, [connection] { connection->start(); });
+        boost::asio::ssl::stream<Adaptor> stream(std::move(*socket),
+                                                 *adaptorCtx);
+        using ConnectionType = Connection<Adaptor, Handler>;
+        auto connection = std::make_shared<ConnectionType>(
+            handler, httpType, std::move(timer), getCachedDateStr,
+            std::move(stream));
+
+        boost::asio::post(getIoContext(),
+                          [connection] { connection->start(); });
 
         doAccept();
     }
 
     void doAccept()
     {
-        SocketPtr socket = std::make_unique<Socket>(ioService);
+        SocketPtr socket = std::make_unique<Adaptor>(getIoContext());
         // Keep a raw pointer so when the socket is moved, the pointer is still
         // valid
-        Socket* socketPtr = socket.get();
-
-        acceptor.async_accept(
-            *socketPtr,
-            std::bind_front(&self_t::afterAccept, this, std::move(socket)));
+        Adaptor* socketPtr = socket.get();
+        for (Acceptor& accept : acceptors)
+        {
+            accept.acceptor.async_accept(
+                *socketPtr,
+                std::bind_front(&self_t::afterAccept, this, std::move(socket),
+                                accept.httpType));
+        }
     }
 
   private:
-    boost::asio::io_context& ioService;
     std::function<std::string()> getCachedDateStr;
-    boost::asio::ip::tcp::acceptor acceptor;
+    std::vector<Acceptor> acceptors;
     boost::asio::signal_set signals;
 
     std::string dateStr;
diff --git a/http/test_stream.hpp b/http/test_stream.hpp
new file mode 100644
index 0000000..dbbf04d
--- /dev/null
+++ b/http/test_stream.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/beast/_experimental/test/stream.hpp>
+
+namespace crow
+{
+
+/*
+A test class that simulates a socket by wrapping the beast test stream
+
+Additionally it adds remote_endpoint to allow testing of TCP-specific behaviors
+*/
+struct TestStream : public boost::beast::test::stream
+{
+    explicit TestStream(boost::asio::io_context& io) :
+        boost::beast::test::stream(io)
+    {}
+
+    using endpoint = boost::asio::ip::tcp::endpoint;
+    // NOLINTNEXTLINE(readability-identifier-naming)
+    static endpoint remote_endpoint(boost::system::error_code& ec)
+    {
+        ec = {};
+        return {};
+    }
+};
+
+} // namespace crow