Allow async resolver to be optional

This commit adds a meson option to allow selecting which dns resolver
bmcweb uses.  There are use cases, like Open Compute Project Inband
Management Agent, that would require not using dbus, which would require
us to fall back to the asio resolver.  This commit makes the existing
asio resolver constructor, and async_resolve methods match the
equivalents in asio (which we intended to do anyway), then adds a macro
and configure option for being able to select which resolver backend to
rely on.

Tested: Code can now compile without sdbusplus.

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I3220214367179f131a60082bdfaf7e725d35c125
diff --git a/http/app.hpp b/http/app.hpp
index a3a6e16..d3cf48c 100644
--- a/http/app.hpp
+++ b/http/app.hpp
@@ -183,6 +183,11 @@
     }
 #endif
 
+    boost::asio::io_context& ioContext()
+    {
+        return *io;
+    }
+
   private:
     std::shared_ptr<boost::asio::io_context> io;
 #ifdef BMCWEB_ENABLE_SSL
diff --git a/http/http_client.hpp b/http/http_client.hpp
index 2b498b7..7a98b54 100644
--- a/http/http_client.hpp
+++ b/http/http_client.hpp
@@ -146,7 +146,14 @@
 
     // Ascync callables
     std::function<void(bool, uint32_t, Response&)> callback;
-    crow::async_resolve::Resolver resolver;
+
+#ifdef BMCWEB_DBUS_DNS_RESOLVER
+    using Resolver = crow::async_resolve::Resolver;
+#else
+    using Resolver = boost::asio::ip::tcp::resolver;
+#endif
+    Resolver resolver;
+
     boost::asio::ip::tcp::socket conn;
     std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
         sslConn;
@@ -162,15 +169,14 @@
                          << std::to_string(port)
                          << ", id: " << std::to_string(connId);
 
-        resolver.asyncResolve(host, port,
-                              std::bind_front(&ConnectionInfo::afterResolve,
-                                              this, shared_from_this()));
+        resolver.async_resolve(host, std::to_string(port),
+                               std::bind_front(&ConnectionInfo::afterResolve,
+                                               this, shared_from_this()));
     }
 
-    void afterResolve(
-        const std::shared_ptr<ConnectionInfo>& /*self*/,
-        const boost::beast::error_code& ec,
-        const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
+    void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
+                      const boost::system::error_code& ec,
+                      const Resolver::results_type& endpointList)
     {
         if (ec || (endpointList.empty()))
         {
@@ -591,7 +597,7 @@
         unsigned int connIdIn) :
         subId(idIn),
         connPolicy(connPolicyIn), host(destIPIn), port(destPortIn),
-        connId(connIdIn), conn(iocIn), timer(iocIn)
+        connId(connIdIn), resolver(iocIn), conn(iocIn), timer(iocIn)
     {
         if (useSSL)
         {
@@ -833,8 +839,7 @@
   private:
     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
         connectionPools;
-    boost::asio::io_context& ioc =
-        crow::connections::systemBus->get_io_context();
+    boost::asio::io_context& ioc;
     std::shared_ptr<ConnectionPolicy> connPolicy;
 
     // Used as a dummy callback by sendData() in order to call
@@ -847,9 +852,12 @@
 
   public:
     HttpClient() = delete;
-    explicit HttpClient(const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
+    explicit HttpClient(boost::asio::io_context& iocIn,
+                        const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
+        ioc(iocIn),
         connPolicy(connPolicyIn)
     {}
+
     HttpClient(const HttpClient&) = delete;
     HttpClient& operator=(const HttpClient&) = delete;
     HttpClient(HttpClient&&) = delete;
diff --git a/include/async_resolve.hpp b/include/async_resolve.hpp
index f44be4b..5e526ca 100644
--- a/include/async_resolve.hpp
+++ b/include/async_resolve.hpp
@@ -1,4 +1,5 @@
 #pragma once
+#ifdef BMCWEB_DBUS_DNS_RESOLVER
 #include "dbus_singleton.hpp"
 #include "logging.hpp"
 
@@ -20,7 +21,9 @@
 class Resolver
 {
   public:
-    Resolver() = default;
+    // unused io param used to keep interface identical to
+    // boost::asio::tcp:::resolver
+    explicit Resolver(boost::asio::io_context& /*io*/) {}
 
     ~Resolver() = default;
 
@@ -29,19 +32,37 @@
     Resolver& operator=(const Resolver&) = delete;
     Resolver& operator=(Resolver&&) = delete;
 
+    using results_type = std::vector<boost::asio::ip::tcp::endpoint>;
+
     template <typename ResolveHandler>
-    void asyncResolve(const std::string& host, uint16_t port,
-                      ResolveHandler&& handler)
+    // This function is kept using snake case so that it is interoperable with
+    // boost::asio::ip::tcp::resolver
+    // NOLINTNEXTLINE(readability-identifier-naming)
+    void async_resolve(const std::string& host, std::string_view port,
+                       ResolveHandler&& handler)
     {
         BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
+
+        uint16_t portNum = 0;
+
+        auto it = std::from_chars(&*port.begin(), &*port.end(), portNum);
+        if (it.ec != std::errc())
+        {
+            BMCWEB_LOG_ERROR << "Failed to get the Port";
+            handler(std::make_error_code(std::errc::invalid_argument),
+                    results_type{});
+
+            return;
+        }
+
         uint64_t flag = 0;
         crow::connections::systemBus->async_method_call(
-            [host, port, handler{std::forward<ResolveHandler>(handler)}](
+            [host, portNum, handler{std::forward<ResolveHandler>(handler)}](
                 const boost::system::error_code& ec,
                 const std::vector<
                     std::tuple<int32_t, int32_t, std::vector<uint8_t>>>& resp,
                 const std::string& hostName, const uint64_t flagNum) {
-            std::vector<boost::asio::ip::tcp::endpoint> endpointList;
+            results_type endpointList;
             if (ec)
             {
                 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
@@ -51,9 +72,11 @@
             BMCWEB_LOG_DEBUG << "ResolveHostname returned: " << hostName << ":"
                              << flagNum;
             // Extract the IP address from the response
-            for (auto resolveList : resp)
+            for (const std::tuple<int32_t, int32_t, std::vector<uint8_t>>&
+                     resolveList : resp)
             {
-                std::vector<uint8_t> ipAddress = std::get<2>(resolveList);
+                const std::vector<uint8_t>& ipAddress =
+                    std::get<2>(resolveList);
                 boost::asio::ip::tcp::endpoint endpoint;
                 if (ipAddress.size() == 4) // ipv4 address
                 {
@@ -81,7 +104,7 @@
                     handler(ec, endpointList);
                     return;
                 }
-                endpoint.port(port);
+                endpoint.port(portNum);
                 BMCWEB_LOG_DEBUG << "resolved endpoint is : " << endpoint;
                 endpointList.push_back(endpoint);
             }
@@ -96,3 +119,4 @@
 
 } // namespace async_resolve
 } // namespace crow
+#endif
diff --git a/meson.build b/meson.build
index 05e5e2e..46d6e03 100644
--- a/meson.build
+++ b/meson.build
@@ -41,6 +41,10 @@
   add_project_arguments('-DNDEBUG', language : 'cpp')
 endif
 
+if(get_option('dns-resolver') == 'systemd-dbus')
+  add_project_arguments('-DBMCWEB_DBUS_DNS_RESOLVER', language : 'cpp')
+endif
+
 # Disable lto when compiling with no optimization
 if(get_option('optimization') == '0')
   add_project_arguments('-fno-lto', language: 'cpp')
diff --git a/meson_options.txt b/meson_options.txt
index 8a440f9..3c2d3f7 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -236,6 +236,17 @@
 )
 
 option(
+    'dns-resolver',
+    type: 'combo',
+    choices: ['systemd-dbus', 'asio'],
+    value: 'systemd-dbus',
+    description: '''Sets which DNS resolver backend should be used.
+    systemd-dbus uses the Systemd ResolveHostname on dbus, but requires dbus
+    support.  asio relies on boost::asio::tcp::resolver, but cannot resolve
+    names when boost threading is disabled.'''
+)
+
+option(
     'redfish-aggregation',
     type: 'feature',
     value: 'disabled',
diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
index 6c2a569..7ae47dd 100644
--- a/redfish-core/include/event_service_manager.hpp
+++ b/redfish-core/include/event_service_manager.hpp
@@ -378,22 +378,16 @@
     Subscription& operator=(Subscription&&) = delete;
 
     Subscription(const std::string& inHost, uint16_t inPort,
-                 const std::string& inPath, const std::string& inUriProto) :
+                 const std::string& inPath, const std::string& inUriProto,
+                 boost::asio::io_context& ioc) :
         host(inHost),
         port(inPort), policy(std::make_shared<crow::ConnectionPolicy>()),
-        client(policy), path(inPath), uriProto(inUriProto)
+        client(ioc, policy), path(inPath), uriProto(inUriProto)
     {
         // Subscription constructor
         policy->invalidResp = retryRespHandler;
     }
 
-    explicit Subscription(
-        const std::shared_ptr<boost::asio::ip::tcp::socket>& adaptor) :
-        policy(std::make_shared<crow::ConnectionPolicy>()),
-        client(policy),
-        sseConn(std::make_shared<crow::ServerSentEvents>(adaptor))
-    {}
-
     ~Subscription() = default;
 
     bool sendEvent(std::string& msg)
@@ -602,12 +596,6 @@
     uint32_t retryAttempts = 0;
     uint32_t retryTimeoutInterval = 0;
 
-    EventServiceManager()
-    {
-        // Load config from persist store.
-        initConfig();
-    }
-
     std::streampos redfishLogFilePosition{0};
     size_t noOfEventLogSubscribers{0};
     size_t noOfMetricReportSubscribers{0};
@@ -617,6 +605,8 @@
 
     uint64_t eventId{1};
 
+    boost::asio::io_context& ioc;
+
   public:
     EventServiceManager(const EventServiceManager&) = delete;
     EventServiceManager& operator=(const EventServiceManager&) = delete;
@@ -624,9 +614,16 @@
     EventServiceManager& operator=(EventServiceManager&&) = delete;
     ~EventServiceManager() = default;
 
-    static EventServiceManager& getInstance()
+    explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn)
     {
-        static EventServiceManager handler;
+        // Load config from persist store.
+        initConfig();
+    }
+
+    static EventServiceManager&
+        getInstance(boost::asio::io_context* ioc = nullptr)
+    {
+        static EventServiceManager handler(*ioc);
         return handler;
     }
 
@@ -662,7 +659,7 @@
                 continue;
             }
             std::shared_ptr<Subscription> subValue =
-                std::make_shared<Subscription>(host, port, path, urlProto);
+                std::make_shared<Subscription>(host, port, path, urlProto, ioc);
 
             subValue->id = newSub->id;
             subValue->destinationUrl = newSub->destinationUrl;
diff --git a/redfish-core/include/redfish_aggregator.hpp b/redfish-core/include/redfish_aggregator.hpp
index 1c514ec..0407333 100644
--- a/redfish-core/include/redfish_aggregator.hpp
+++ b/redfish-core/include/redfish_aggregator.hpp
@@ -374,12 +374,6 @@
   private:
     crow::HttpClient client;
 
-    RedfishAggregator() :
-        client(std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
-    {
-        getSatelliteConfigs(constructorCallback);
-    }
-
     // Dummy callback used by the Constructor so that it can report the number
     // of satellite configs when the class is first created
     static void constructorCallback(
@@ -735,15 +729,21 @@
     }
 
   public:
+    explicit RedfishAggregator(boost::asio::io_context& ioc) :
+        client(ioc,
+               std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy()))
+    {
+        getSatelliteConfigs(constructorCallback);
+    }
     RedfishAggregator(const RedfishAggregator&) = delete;
     RedfishAggregator& operator=(const RedfishAggregator&) = delete;
     RedfishAggregator(RedfishAggregator&&) = delete;
     RedfishAggregator& operator=(RedfishAggregator&&) = delete;
     ~RedfishAggregator() = default;
 
-    static RedfishAggregator& getInstance()
+    static RedfishAggregator& getInstance(boost::asio::io_context* io = nullptr)
     {
-        static RedfishAggregator handler;
+        static RedfishAggregator handler(*io);
         return handler;
     }
 
diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
index f74f215..5f57ff7 100644
--- a/redfish-core/lib/event_service.hpp
+++ b/redfish-core/lib/event_service.hpp
@@ -281,8 +281,8 @@
         {
             path = "/";
         }
-        std::shared_ptr<Subscription> subValue =
-            std::make_shared<Subscription>(host, port, path, urlProto);
+        std::shared_ptr<Subscription> subValue = std::make_shared<Subscription>(
+            host, port, path, urlProto, app.ioContext());
 
         subValue->destinationUrl = destUrl;
 
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index d3021a8..8150f46 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -85,11 +85,11 @@
     redfish::RedfishService redfish(app);
 
     // Create EventServiceManager instance and initialize Config
-    redfish::EventServiceManager::getInstance();
+    redfish::EventServiceManager::getInstance(&*io);
 
 #ifdef BMCWEB_ENABLE_REDFISH_AGGREGATION
     // Create RedfishAggregator instance and initialize Config
-    redfish::RedfishAggregator::getInstance();
+    redfish::RedfishAggregator::getInstance(&*io);
 #endif
 #endif