diff options
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.patch | 547 |
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 + |