From f388587781c3d874b13b50ad39e8674f0bc08049 Mon Sep 17 00:00:00 2001 From: AppaRao Puli Date: Mon, 25 May 2020 16:14:39 +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 --- http/http_client.hpp | 270 +++++++++++++++++-------- redfish-core/include/event_service_manager.hpp | 2 +- 2 files changed, 186 insertions(+), 86 deletions(-) diff --git a/http/http_client.hpp b/http/http_client.hpp index e6a7db1..27d2af3 100644 --- a/http/http_client.hpp +++ b/http/http_client.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,10 @@ enum class ConnState class HttpClient : public std::enable_shared_from_this { private: - boost::beast::tcp_stream conn; + boost::asio::io_context& ioc; + boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client}; + std::shared_ptr> sslConn; + std::shared_ptr conn; boost::asio::steady_timer timer; boost::beast::flat_buffer buffer; boost::beast::http::request req; @@ -62,14 +66,37 @@ class HttpClient : public std::enable_shared_from_this 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; + inline boost::beast::tcp_stream& getConn() + { + if (useSsl) + { + return (boost::beast::get_lowest_layer(*sslConn)); + } + else + { + return (*conn); + } + } + void doConnect() { + if (useSsl) + { + sslConn = std::make_shared< + boost::beast::ssl_stream>(ioc, ctx); + } + else + { + conn = std::make_shared(ioc); + } + if (state == ConnState::connectInProgress) { return; @@ -77,25 +104,53 @@ class HttpClient : public std::enable_shared_from_this state = ConnState::connectInProgress; BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; - // Set a timeout on the operation - 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; - self->checkQueue(); - }); + 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->useSsl) + { + self->performHandshake(); + } + else + { + self->state = ConnState::connected; + self->checkQueue(); + } + }; + + getConn().expires_after(std::chrono::seconds(30)); + getConn().async_connect(endpoint, std::move(respHandler)); + } + + void performHandshake() + { + 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->state = ConnState::connectFailed; + self->doCloseAndCheckQueue(); + return; + } + self->state = ConnState::connected; + BMCWEB_LOG_DEBUG << "SSL Handshake successfull \n"; + + self->checkQueue(); + }); } void sendMessage(const std::string& data) @@ -108,7 +163,10 @@ class HttpClient : public std::enable_shared_from_this BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; - req.version(static_cast(11)); // HTTP 1.1 + req = {}; + res = {}; + + req.version(11); // HTTP 1.1 req.target(uri); req.method(boost::beast::http::verb::post); @@ -123,83 +181,121 @@ class HttpClient : public std::enable_shared_from_this 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->state = ConnState::sendFailed; + self->doCloseAndCheckQueue(); + 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(); - }); + getConn().expires_after(std::chrono::seconds(30)); + if (useSsl) + { + 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) { - if (ec) - { - BMCWEB_LOG_ERROR << "recvMessage() failed: " - << ec.message(); - self->state = ConnState::recvFailed; - self->checkQueue(); - return; - } - BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " - << bytesTransferred; - boost::ignore_unused(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->state = ConnState::recvFailed; + self->doCloseAndCheckQueue(); + return; + } + 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; + // 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; - self->checkQueue(); - }); + // Send is successful, Lets remove data from queue + // check for next request data in queue. + self->requestDataQueue.pop(); + self->state = ConnState::idle; + + if (ec == boost::beast::http::error::partial_message) + { + // Least bothered about recv message. Partial + // message means, already data is sent. Lets close + // connection and let next request open connection + // to avoid truncated stream. + self->state = ConnState::closed; + self->doCloseAndCheckQueue(); + return; + } + + self->checkQueue(); + }; + + getConn().expires_after(std::chrono::seconds(30)); + if (useSsl) + { + boost::beast::http::async_read(*sslConn, buffer, res, + std::move(respHandler)); + } + else + { + boost::beast::http::async_read(*conn, buffer, res, + std::move(respHandler)); + } } - void doClose() + void doCloseAndCheckQueue() { boost::beast::error_code ec; - conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + getConn().cancel(); + getConn().expires_after(std::chrono::seconds(30)); + getConn().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) + if (ec && ec != boost::asio::error::eof) { - BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); - 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_DEBUG + << "doCloseAndCheckQueue(): Connection closed by server."; + } + else + { + BMCWEB_LOG_ERROR << "doCloseAndCheckQueue() failed: " + << ec.message(); + } } + + getConn().close(); BMCWEB_LOG_DEBUG << "Connection closed gracefully"; + checkQueue(); + 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; } @@ -257,17 +353,20 @@ class HttpClient : public std::enable_shared_from_this BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs << " seconds. RetryCount = " << retryCount; timer.expires_after(std::chrono::seconds(retryIntervalSecs)); - timer.async_wait([self = shared_from_this()]( - const boost::system::error_code& ec) { - self->runningTimer = false; - self->connStateCheck(); - }); + timer.async_wait( + [self = shared_from_this()](boost::system::error_code) { + self->runningTimer = false; + self->connStateCheck(); + }); return; } else { - // reset retry count. - retryCount = 0; + if (state == ConnState::idle) + { + // State idle means, previous attempt is successful. + retryCount = 0; + } } connStateCheck(); @@ -310,10 +409,11 @@ class HttpClient : public std::enable_shared_from_this 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), + const std::string& destUri, + const bool inUseSsl = true) : + ioc(ioc), timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri), - retryCount(0), maxRetryAttempts(5), + useSsl(inUseSsl), retryCount(0), maxRetryAttempts(5), retryPolicyAction("TerminateAfterRetries"), runningTimer(false) { boost::asio::ip::tcp::resolver resolver(ioc); diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp index 6362112..3ab2605 100644 --- a/redfish-core/include/event_service_manager.hpp +++ b/redfish-core/include/event_service_manager.hpp @@ -383,7 +383,7 @@ class Subscription { conn = std::make_shared( crow::connections::systemBus->get_io_context(), id, host, port, - path); + path, (uriProto == "https" ? true : false)); } Subscription(const std::shared_ptr& adaptor) : -- 2.7.4