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>
diff --git a/http/http_client.hpp b/http/http_client.hpp
index feabbba..aaf1b2d 100644
--- a/http/http_client.hpp
+++ b/http/http_client.hpp
@@ -20,6 +20,7 @@
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/http/message.hpp>
+#include <boost/beast/ssl/ssl_stream.hpp>
#include <boost/beast/version.hpp>
#include <include/async_resolve.hpp>
@@ -44,6 +45,8 @@
resolved,
connectInProgress,
connectFailed,
+ handshakeInProgress,
+ handshakeFailed,
connected,
sendInProgress,
sendFailed,
@@ -62,7 +65,9 @@
{
private:
crow::async_resolve::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_static_buffer<httpReadBodyLimit> buffer;
boost::beast::http::request<boost::beast::http::string_body> req;
@@ -111,23 +116,52 @@
void doConnect()
{
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(
- endPoints, [self(shared_from_this())](
- const boost::beast::error_code ec,
- const boost::asio::ip::tcp::endpoint& endpoint) {
+ conn.async_connect(endPoints, 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();
});
@@ -135,106 +169,159 @@
void sendMessage(const std::string& data)
{
- state = ConnState::sendInProgress;
-
BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
+ state = ConnState::sendInProgress;
req.body() = data;
req.prepare_payload();
- // 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);
+ 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;
+ }
- self->recvMessage();
- });
+ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
+ << bytesTransferred;
+ boost::ignore_unused(bytesTransferred);
+ 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()
{
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);
+ // 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);
-
- // 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) {
- if (ec)
- {
- BMCWEB_LOG_ERROR << "recvMessage() failed: "
- << ec.message();
- self->state = ConnState::recvFailed;
- self->handleConnState();
- return;
- }
- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
- << bytesTransferred;
- BMCWEB_LOG_DEBUG << "recvMessage() data: "
- << self->parser->get();
-
- // 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();
- });
+ 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;
- 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)
+ // Set the timeout on the tcp stream socket for the async operation
+ conn.expires_after(std::chrono::seconds(30));
+ if (sslConn)
{
- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
- return;
+ sslConn->async_shutdown([self = shared_from_this()](
+ const boost::system::error_code ec) {
+ if (ec)
+ {
+ // 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();
+ }
+ }
+ else
+ {
+ BMCWEB_LOG_DEBUG << "Connection closed gracefully...";
+ }
+ self->conn.close();
+
+ if ((self->state != ConnState::suspended) &&
+ (self->state != ConnState::terminated))
+ {
+ self->state = ConnState::closed;
+ self->handleConnState();
+ }
+ });
}
- 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();
+ }
}
}
@@ -301,6 +388,7 @@
{
case ConnState::resolveInProgress:
case ConnState::connectInProgress:
+ case ConnState::handshakeInProgress:
case ConnState::sendInProgress:
case ConnState::recvInProgress:
case ConnState::closeInProgress:
@@ -332,6 +420,7 @@
}
case ConnState::resolveFailed:
case ConnState::connectFailed:
+ case ConnState::handshakeFailed:
case ConnState::sendFailed:
case ConnState::recvFailed:
case ConnState::retry:
@@ -370,7 +459,8 @@
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),
@@ -383,8 +473,11 @@
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 c999121..267c857 100644
--- a/redfish-core/include/event_service_manager.hpp
+++ b/redfish-core/include/event_service_manager.hpp
@@ -398,7 +398,7 @@
{
conn = std::make_shared<crow::HttpClient>(
crow::connections::systemBus->get_io_context(), id, host, port,
- path);
+ path, uriProto);
}
Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) :