From 3f2ad28e6e124249cde3df50c9e18c283fbcbf3e Mon Sep 17 00:00:00 2001 From: AppaRao Puli Date: Mon, 22 Feb 2021 17:07:47 +0000 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 | 307 ++++++++++++------ .../include/event_service_manager.hpp | 2 +- 2 files changed, 202 insertions(+), 107 deletions(-) diff --git a/http/http_client.hpp b/http/http_client.hpp index aad1cce..5e7ff47 100644 --- a/http/http_client.hpp +++ b/http/http_client.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,8 @@ enum class ConnState resolveFailed, connectInProgress, connectFailed, + handshakeInProgress, + handshakeFailed, connected, sendInProgress, sendFailed, @@ -61,7 +64,9 @@ class HttpClient : public std::enable_shared_from_this { private: crow::async_resolve::Resolver resolver; + boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client}; boost::beast::tcp_stream conn; + std::optional> sslConn; boost::asio::steady_timer timer; boost::beast::flat_static_buffer buffer; boost::beast::http::request req; @@ -108,23 +113,52 @@ class HttpClient : public std::enable_shared_from_this const std::vector& endpointList) { state = ConnState::connectInProgress; + sslConn.emplace(conn, ctx); BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; + auto respHandler = [self(shared_from_this())]( + const boost::beast::error_code ec, + const boost::asio::ip::tcp::endpoint& endpoint) { + if (ec) + { + BMCWEB_LOG_ERROR << "Connect " << endpoint + << " failed: " << ec.message(); + self->state = ConnState::connectFailed; + self->handleConnState(); + return; + } + BMCWEB_LOG_DEBUG << "Connected to: " << endpoint; + if (self->sslConn) + { + self->performHandshake(); + } + else + { + self->handleConnState(); + } + }; conn.expires_after(std::chrono::seconds(30)); - conn.async_connect( - endpointList, [self(shared_from_this())]( - const boost::beast::error_code ec, - const boost::asio::ip::tcp::endpoint& endpoint) { + conn.async_connect(endpointList, std::move(respHandler)); + } + + void performHandshake() + { + state = ConnState::handshakeInProgress; + + sslConn->async_handshake( + boost::asio::ssl::stream_base::client, + [self(shared_from_this())](const boost::beast::error_code ec) { if (ec) { - BMCWEB_LOG_ERROR << "Connect " << endpoint - << " failed: " << ec.message(); - self->state = ConnState::connectFailed; + BMCWEB_LOG_ERROR << "SSL handshake failed: " + << ec.message(); + self->state = ConnState::handshakeFailed; self->handleConnState(); return; } - BMCWEB_LOG_DEBUG << "Connected to: " << endpoint; + + BMCWEB_LOG_DEBUG << "SSL Handshake successfull"; self->state = ConnState::connected; self->handleConnState(); }); @@ -132,132 +166,187 @@ class HttpClient : public std::enable_shared_from_this void sendMessage(const std::string& data) { - state = ConnState::sendInProgress; - BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; + state = ConnState::sendInProgress; 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->handleConnState(); + return; + } - // 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->handleConnState(); - return; - } - BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " - << bytesTransferred; - boost::ignore_unused(bytesTransferred); + BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " + << bytesTransferred; + boost::ignore_unused(bytesTransferred); + self->recvMessage(); + }; - self->recvMessage(); - }); + // Set a timeout on the operation + 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() { state = ConnState::recvInProgress; + auto respHandler = [self(shared_from_this())]( + const boost::beast::error_code ec, + const std::size_t& bytesTransferred) { + if (ec && ec != boost::asio::ssl::error::stream_truncated) + { + BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); + + self->state = ConnState::recvFailed; + self->handleConnState(); + return; + } + + BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " + << bytesTransferred; + boost::ignore_unused(bytesTransferred); + + // Check if the response and header are received + if (!self->parser->is_done()) + { + // The parser failed to receive the response + BMCWEB_LOG_ERROR + << "recvMessage() parser failed to receive response"; + self->state = ConnState::recvFailed; + self->handleConnState(); + return; + } + + unsigned int respCode = self->parser->get().result_int(); + BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " + << respCode; + + // 2XX response is considered to be successful + if ((respCode < 200) || (respCode >= 300)) + { + // The listener failed to receive the Sent-Event + BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " + "receive Sent-Event"; + self->state = ConnState::recvFailed; + self->handleConnState(); + return; + } + + // Send is successful, Lets remove data from queue + // check for next request data in queue. + if (!self->requestDataQueue.empty()) + { + self->requestDataQueue.pop_front(); + } + self->state = ConnState::idle; + // Keep the connection alive if server supports it + // Else close the connection + BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " + << self->parser->keep_alive(); + if (!self->parser->keep_alive()) + { + // Abort the connection since server is not keep-alive enabled + self->state = ConnState::abortConnection; + } + + // Returns ownership of the parsed message + self->parser->release(); + + self->handleConnState(); + }; parser.emplace(std::piecewise_construct, std::make_tuple()); parser->body_limit(httpReadBodyLimit); // Check only for the response header parser->skip(true); + 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 doClose() + { + state = ConnState::closeInProgress; - // Receive the HTTP response - boost::beast::http::async_read( - conn, buffer, *parser, - [self(shared_from_this())](const boost::beast::error_code& ec, - const std::size_t& bytesTransferred) { + // Set the timeout on the tcp stream socket for the async operation + conn.expires_after(std::chrono::seconds(30)); + if (sslConn) + { + sslConn->async_shutdown([self = shared_from_this()]( + const boost::system::error_code ec) { if (ec) { - BMCWEB_LOG_ERROR << "recvMessage() failed: " - << ec.message(); - self->state = ConnState::recvFailed; - self->handleConnState(); - 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_INFO << "doClose(): Connection " + "closed by server. "; + } + else + { + BMCWEB_LOG_ERROR << "doClose() failed: " + << ec.message(); + } } - BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " - << bytesTransferred; - BMCWEB_LOG_DEBUG << "recvMessage() data: " - << self->parser->get(); - - // Check if the response and header are received - if (!self->parser->is_done()) + else { - // The parser failed to receive the response - BMCWEB_LOG_ERROR - << "recvMessage() parser failed to receive response"; - self->state = ConnState::recvFailed; - self->handleConnState(); - return; + BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; } + self->conn.close(); - unsigned int respCode = self->parser->get().result_int(); - BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " - << respCode; - - // 2XX response is considered to be successful - if ((respCode < 200) || (respCode >= 300)) + if ((self->state != ConnState::suspended) && + (self->state != ConnState::terminated)) { - // The listener failed to receive the Sent-Event - BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " - "receive Sent-Event"; - self->state = ConnState::recvFailed; + self->state = ConnState::closed; self->handleConnState(); - return; } - - // Send is successful, Lets remove data from queue - // check for next request data in queue. - if (!self->requestDataQueue.empty()) - { - self->requestDataQueue.pop_front(); - } - self->state = ConnState::idle; - - // Keep the connection alive if server supports it - // Else close the connection - BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " - << self->parser->keep_alive(); - if (!self->parser->keep_alive()) - { - // Abort the connection since server is not keep-alive - // enabled - self->state = ConnState::abortConnection; - } - - self->handleConnState(); }); - } - - void doClose() - { - state = ConnState::closeInProgress; - boost::beast::error_code ec; - conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); - conn.close(); - - // not_connected happens sometimes so don't bother reporting it. - if (ec && ec != boost::beast::errc::not_connected) - { - BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); - return; } - BMCWEB_LOG_DEBUG << "Connection closed gracefully"; - if ((state != ConnState::suspended) && (state != ConnState::terminated)) + else { - state = ConnState::closed; - handleConnState(); + boost::beast::error_code ec; + conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, + ec); + if (ec) + { + BMCWEB_LOG_ERROR << "doClose() failed: " << ec.message(); + } + else + { + BMCWEB_LOG_DEBUG << "Connection closed gracefully..."; + } + conn.close(); + + if ((state != ConnState::suspended) && + (state != ConnState::terminated)) + { + state = ConnState::closed; + handleConnState(); + } } } @@ -330,6 +419,7 @@ class HttpClient : public std::enable_shared_from_this { case ConnState::resolveInProgress: case ConnState::connectInProgress: + case ConnState::handshakeInProgress: case ConnState::sendInProgress: case ConnState::recvInProgress: case ConnState::closeInProgress: @@ -356,6 +446,7 @@ class HttpClient : public std::enable_shared_from_this } case ConnState::resolveFailed: case ConnState::connectFailed: + case ConnState::handshakeFailed: case ConnState::sendFailed: case ConnState::recvFailed: case ConnState::retry: @@ -394,7 +485,8 @@ 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) : + const std::string& destUri, + const std::string& uriProto) : conn(ioc), timer(ioc), req(boost::beast::http::verb::post, destUri, 11), state(ConnState::initialized), subId(id), host(destIP), port(destPort), @@ -407,8 +499,11 @@ class HttpClient : public std::enable_shared_from_this req.keep_alive(true); requestDataQueue.set_capacity(maxRequestQueueSize); + if (uriProto == "https") + { + sslConn.emplace(conn, ctx); + } } - void sendData(const std::string& data) { if ((state == ConnState::suspended) || (state == ConnState::terminated)) diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp index 08d0b98..f1ce0c0 100644 --- a/redfish-core/include/event_service_manager.hpp +++ b/redfish-core/include/event_service_manager.hpp @@ -385,7 +385,7 @@ class Subscription : public persistent_data::UserSubscription { conn = std::make_shared( crow::connections::systemBus->get_io_context(), id, host, port, - path); + path, uriProto); } Subscription(const std::shared_ptr& adaptor) : -- 2.17.1