summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch547
1 files changed, 547 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch
new file mode 100644
index 000000000..54f00aa39
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/0005-EventService-https-client-support.patch
@@ -0,0 +1,547 @@
+From d340953bc925ff8535c5a8fac54db24b243ba8ad Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Mon, 19 Oct 2020 13:21:42 +0530
+Subject: [PATCH] EventService: https client support
+
+Add https client support for push style
+eventing. Using this BMC can push the event
+logs/telemetry data to event listener over
+secure http channel.
+
+Tested:
+ - Created subscription with https destination
+ url. Using SubmitTestEvent action set the
+ event and can see event on event listener.
+ - Validator passed.
+
+Change-Id: I44c3918b39baa2eb5fddda9d635f99aa280a422a
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+Signed-off-by: P Dheeraj Srujan Kumar <p.dheeraj.srujan.kumar@intel.com>
+---
+ http/http_client.hpp | 370 +++++++++++++-----
+ .../include/event_service_manager.hpp | 2 +-
+ 2 files changed, 267 insertions(+), 105 deletions(-)
+
+diff --git a/http/http_client.hpp b/http/http_client.hpp
+index 5c7b13f..d782dee 100644
+--- a/http/http_client.hpp
++++ b/http/http_client.hpp
+@@ -31,12 +31,17 @@ namespace crow
+ {
+
+ static constexpr uint8_t maxRequestQueueSize = 50;
++static constexpr unsigned int httpReadBodyLimit = 1024;
+
+ enum class ConnState
+ {
+ initialized,
++ resolveInProgress,
++ resolveFailed,
++ resolved,
+ connectInProgress,
+ connectFailed,
++ sslHandshakeInProgress,
+ connected,
+ sendInProgress,
+ sendFailed,
+@@ -50,53 +55,124 @@ enum class ConnState
+ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ {
+ private:
++ boost::asio::ip::tcp::resolver resolver;
++ boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client};
+ boost::beast::tcp_stream conn;
++ std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream&>> sslConn;
+ boost::asio::steady_timer timer;
+- boost::beast::flat_buffer buffer;
++ boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
++ std::optional<
++ boost::beast::http::response_parser<boost::beast::http::string_body>>
++ parser;
+ boost::beast::http::request<boost::beast::http::string_body> req;
+- boost::beast::http::response<boost::beast::http::string_body> res;
+ boost::asio::ip::tcp::resolver::results_type endpoint;
+- std::vector<std::pair<std::string, std::string>> headers;
++ boost::beast::http::fields fields;
+ std::queue<std::string> requestDataQueue;
+- ConnState state;
+ std::string subId;
+ std::string host;
+ std::string port;
+ std::string uri;
++ bool useSsl;
+ uint32_t retryCount;
+ uint32_t maxRetryAttempts;
+ uint32_t retryIntervalSecs;
+ std::string retryPolicyAction;
+ bool runningTimer;
++ ConnState state;
++
++ void doResolve()
++ {
++ BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
++ if (state == ConnState::resolveInProgress)
++ {
++ return;
++ }
++ state = ConnState::resolveInProgress;
++ // TODO: Use async_resolver. boost asio example
++ // code as is crashing with async_resolve().
++ try
++ {
++ endpoint = resolver.resolve(host, port);
++ }
++ catch (const std::exception& e)
++ {
++ BMCWEB_LOG_ERROR << "Failed to resolve hostname: " << host << " - "
++ << e.what();
++ state = ConnState::resolveFailed;
++ checkQueue();
++ return;
++ }
++ state = ConnState::resolved;
++ checkQueue();
++ }
+
+ void doConnect()
+ {
+- if (state == ConnState::connectInProgress)
++ if (useSsl)
++ {
++ sslConn.emplace(conn, ctx);
++ }
++
++ if ((state == ConnState::connectInProgress) ||
++ (state == ConnState::sslHandshakeInProgress))
+ {
+ return;
+ }
+ state = ConnState::connectInProgress;
+
+ BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
+- // Set a timeout on the operation
++
++ auto respHandler =
++ [self(shared_from_this())](const boost::beast::error_code ec,
++ const boost::asio::ip::tcp::resolver::
++ results_type::endpoint_type& ep) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "Connect " << ep
++ << " failed: " << ec.message();
++ self->state = ConnState::connectFailed;
++ self->checkQueue();
++ return;
++ }
++ BMCWEB_LOG_DEBUG << "Connected to: " << ep;
++ if (self->sslConn)
++ {
++ self->performHandshake();
++ }
++ else
++ {
++ self->state = ConnState::connected;
++ self->checkQueue();
++ }
++ };
++
+ conn.expires_after(std::chrono::seconds(30));
+- conn.async_connect(endpoint, [self(shared_from_this())](
+- const boost::beast::error_code& ec,
+- const boost::asio::ip::tcp::resolver::
+- results_type::endpoint_type& ep) {
+- if (ec)
+- {
+- BMCWEB_LOG_ERROR << "Connect " << ep
+- << " failed: " << ec.message();
+- self->state = ConnState::connectFailed;
+- self->checkQueue();
+- return;
+- }
+- self->state = ConnState::connected;
+- BMCWEB_LOG_DEBUG << "Connected to: " << ep;
++ conn.async_connect(endpoint, std::move(respHandler));
++ }
++
++ void performHandshake()
++ {
++ if (state == ConnState::sslHandshakeInProgress)
++ {
++ return;
++ }
++ state = ConnState::sslHandshakeInProgress;
++
++ sslConn->async_handshake(
++ boost::asio::ssl::stream_base::client,
++ [self(shared_from_this())](const boost::beast::error_code ec) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "SSL handshake failed: "
++ << ec.message();
++ self->doCloseAndCheckQueue(ConnState::connectFailed);
++ return;
++ }
++ self->state = ConnState::connected;
++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull";
+
+- self->checkQueue();
+- });
++ self->checkQueue();
++ });
+ }
+
+ void sendMessage(const std::string& data)
+@@ -107,100 +183,170 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ }
+ state = ConnState::sendInProgress;
+
+- BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
++ BMCWEB_LOG_DEBUG << host << ":" << port;
+
+- req.version(static_cast<int>(11)); // HTTP 1.1
+- req.target(uri);
+- req.method(boost::beast::http::verb::post);
+-
+- // Set headers
+- for (const auto& [key, value] : headers)
++ req = {};
++ for (const auto& field : fields)
+ {
+- req.set(key, value);
++ req.set(field.name_string(), field.value());
+ }
+ req.set(boost::beast::http::field::host, host);
++ req.set(boost::beast::http::field::content_type, "text/plain");
++
++ req.version(static_cast<int>(11)); // HTTP 1.1
++ req.target(uri);
++ req.method(boost::beast::http::verb::post);
+ req.keep_alive(true);
+
+ req.body() = data;
+ req.prepare_payload();
+
+- // Set a timeout on the operation
+- conn.expires_after(std::chrono::seconds(30));
++ auto respHandler = [self(shared_from_this())](
++ const boost::beast::error_code ec,
++ const std::size_t& bytesTransferred) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
++ self->doCloseAndCheckQueue(ConnState::sendFailed);
++ return;
++ }
++ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
++ << bytesTransferred;
++ boost::ignore_unused(bytesTransferred);
+
+- // Send the HTTP request to the remote host
+- boost::beast::http::async_write(
+- conn, req,
+- [self(shared_from_this())](const boost::beast::error_code& ec,
+- const std::size_t& bytesTransferred) {
+- if (ec)
+- {
+- BMCWEB_LOG_ERROR << "sendMessage() failed: "
+- << ec.message();
+- self->state = ConnState::sendFailed;
+- self->checkQueue();
+- return;
+- }
+- BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
+- << bytesTransferred;
+- boost::ignore_unused(bytesTransferred);
++ self->recvMessage();
++ };
+
+- self->recvMessage();
+- });
++ conn.expires_after(std::chrono::seconds(30));
++ if (sslConn)
++ {
++ boost::beast::http::async_write(*sslConn, req,
++ std::move(respHandler));
++ }
++ else
++ {
++ boost::beast::http::async_write(conn, req, std::move(respHandler));
++ }
+ }
+
+ void recvMessage()
+ {
+- // Receive the HTTP response
+- boost::beast::http::async_read(
+- conn, buffer, res,
+- [self(shared_from_this())](const boost::beast::error_code& ec,
+- const std::size_t& bytesTransferred) {
++ auto respHandler = [self(shared_from_this())](
++ const boost::beast::error_code ec,
++ const std::size_t& bytesTransferred) {
++ if (ec && ec != boost::beast::http::error::partial_message)
++ {
++ BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
++ self->doCloseAndCheckQueue(ConnState::recvFailed);
++ return;
++ }
++ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
++ << bytesTransferred;
++ boost::ignore_unused(bytesTransferred);
++
++ // TODO: check for return status code and perform
++ // retry if fails(Ex: 40x). Take action depending on
++ // retry policy.
++ BMCWEB_LOG_DEBUG << "recvMessage() data: "
++ << self->parser->get().body();
++
++ // Send is successful, Lets remove data from queue
++ // check for next request data in queue.
++ self->requestDataQueue.pop();
++
++ // Transfer ownership of the response
++ self->parser->release();
++
++ // TODO: Implement the keep-alive connections.
++ // Most of the web servers close connection abruptly
++ // and might be reason due to which its observed that
++ // stream_truncated(Next read) or partial_message
++ // errors. So for now, closing connection and re-open
++ // for all cases.
++ self->doCloseAndCheckQueue(ConnState::closed);
++ };
++
++ parser.emplace(std::piecewise_construct, std::make_tuple());
++ parser->body_limit(httpReadBodyLimit);
++ // Since these are all push style eventing, we are not
++ // bothered about response parsing.
++ parser->skip(true);
++ buffer.consume(buffer.size());
++
++ conn.expires_after(std::chrono::seconds(30));
++ if (sslConn)
++ {
++ boost::beast::http::async_read(*sslConn, buffer, *parser,
++ std::move(respHandler));
++ }
++ else
++ {
++ boost::beast::http::async_read(conn, buffer, *parser,
++ std::move(respHandler));
++ }
++ }
++
++ void doCloseAndCheckQueue(const ConnState setState = ConnState::closed)
++ {
++ if (sslConn)
++ {
++ conn.expires_after(std::chrono::seconds(30));
++ sslConn->async_shutdown([self = shared_from_this(),
++ setState{std::move(setState)}](
++ const boost::system::error_code ec) {
+ if (ec)
+ {
+- BMCWEB_LOG_ERROR << "recvMessage() failed: "
+- << ec.message();
+- self->state = ConnState::recvFailed;
+- self->checkQueue();
+- return;
++ // Many https server closes connection abruptly
++ // i.e witnout close_notify. More details are at
++ // https://github.com/boostorg/beast/issues/824
++ if (ec == boost::asio::ssl::error::stream_truncated)
++ {
++ BMCWEB_LOG_ERROR
++ << "doCloseAndCheckQueue(): Connection "
++ "closed by server. ";
++ }
++ else
++ {
++ BMCWEB_LOG_ERROR << "doCloseAndCheckQueue() failed: "
++ << ec.message();
++ }
+ }
+- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
+- << bytesTransferred;
+- boost::ignore_unused(bytesTransferred);
+-
+- // Discard received data. We are not interested.
+- BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
+-
+- // Send is successful, Lets remove data from queue
+- // check for next request data in queue.
+- self->requestDataQueue.pop();
+- self->state = ConnState::idle;
++ else
++ {
++ BMCWEB_LOG_DEBUG << "Connection closed gracefully...";
++ }
++ self->conn.cancel();
++ self->state = setState;
+ self->checkQueue();
+ });
+- }
+-
+- void doClose()
+- {
+- boost::beast::error_code ec;
+- conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+-
+- state = ConnState::closed;
+- // not_connected happens sometimes so don't bother reporting it.
+- if (ec && ec != boost::beast::errc::not_connected)
++ }
++ else
+ {
+- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
+- return;
++ boost::beast::error_code ec;
++ conn.expires_after(std::chrono::seconds(30));
++ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both,
++ ec);
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "doCloseAndCheckQueue() failed: "
++ << ec.message();
++ }
++ else
++ {
++ BMCWEB_LOG_DEBUG << "Connection closed gracefully...";
++ }
++
++ conn.close();
++ state = setState;
++ checkQueue();
+ }
+- BMCWEB_LOG_DEBUG << "Connection closed gracefully";
++ return;
+ }
+
+ void checkQueue(const bool newRecord = false)
+ {
+ if (requestDataQueue.empty())
+ {
+- // TODO: Having issue in keeping connection alive. So lets close if
+- // nothing to be transferred.
+- doClose();
+-
+ BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
+ return;
+ }
+@@ -232,6 +378,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ }
+
+ if ((state == ConnState::connectFailed) ||
++ (state == ConnState::resolveFailed) ||
+ (state == ConnState::sendFailed) ||
+ (state == ConnState::recvFailed))
+ {
+@@ -256,14 +403,18 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ << " seconds. RetryCount = " << retryCount;
+ timer.expires_after(std::chrono::seconds(retryIntervalSecs));
+ timer.async_wait(
+- [self = shared_from_this()](const boost::system::error_code&) {
++ [self = shared_from_this()](boost::system::error_code) {
+ self->runningTimer = false;
+ self->connStateCheck();
+ });
+ return;
+ }
+- // reset retry count.
+- retryCount = 0;
++
++ if (state == ConnState::idle)
++ {
++ // State idle means, previous attempt is successful.
++ retryCount = 0;
++ }
+ connStateCheck();
+
+ return;
+@@ -273,15 +424,21 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ {
+ switch (state)
+ {
++ case ConnState::initialized:
++ case ConnState::resolveFailed:
++ case ConnState::connectFailed:
++ doResolve();
++ break;
+ case ConnState::connectInProgress:
++ case ConnState::resolveInProgress:
++ case ConnState::sslHandshakeInProgress:
+ case ConnState::sendInProgress:
+ case ConnState::suspended:
+ case ConnState::terminated:
+ // do nothing
+ break;
+- case ConnState::initialized:
+ case ConnState::closed:
+- case ConnState::connectFailed:
++ case ConnState::resolved:
+ case ConnState::sendFailed:
+ case ConnState::recvFailed:
+ {
+@@ -297,22 +454,22 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ sendMessage(data);
+ break;
+ }
++ default:
++ break;
+ }
+ }
+
+ public:
+ explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
+ const std::string& destIP, const std::string& destPort,
+- const std::string& destUri) :
+- conn(ioc),
+- timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
+- retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
+- retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
+- {
+- boost::asio::ip::tcp::resolver resolver(ioc);
+- endpoint = resolver.resolve(host, port);
+- state = ConnState::initialized;
+- }
++ const std::string& destUri,
++ const bool inUseSsl = true) :
++ resolver(ioc),
++ conn(ioc), timer(ioc), subId(id), host(destIP), port(destPort),
++ uri(destUri), useSsl(inUseSsl), retryCount(0), maxRetryAttempts(5),
++ retryPolicyAction("TerminateAfterRetries"), runningTimer(false),
++ state(ConnState::initialized)
++ {}
+
+ void sendData(const std::string& data)
+ {
+@@ -337,7 +494,12 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ void setHeaders(
+ const std::vector<std::pair<std::string, std::string>>& httpHeaders)
+ {
+- headers = httpHeaders;
++ // Set headers
++ for (const auto& [key, value] : httpHeaders)
++ {
++ // TODO: Validate the header fileds before assign.
++ fields.set(key, value);
++ }
+ }
+
+ void setRetryConfig(const uint32_t retryAttempts,
+diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
+index 54dafb4..f68ae1d 100644
+--- a/redfish-core/include/event_service_manager.hpp
++++ b/redfish-core/include/event_service_manager.hpp
+@@ -387,7 +387,7 @@ class Subscription
+ {
+ conn = std::make_shared<crow::HttpClient>(
+ crow::connections::systemBus->get_io_context(), id, host, port,
+- path);
++ path, (uriProto == "https" ? true : false));
+ }
+
+ Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) :
+--
+2.17.1
+