From: Francis Dupont Date: Mon, 15 Feb 2021 15:02:31 +0000 (+0100) Subject: [#1661] HTTP code half done X-Git-Tag: Kea-1.9.6~130 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0bdf8b6df6fb680029b07cf6257d06e2581d32cf;p=thirdparty%2Fkea.git [#1661] HTTP code half done --- diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index ab3f83f90c..0562b7dc7d 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = . tests AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) AM_CXXFLAGS = $(KEA_CXXFLAGS) EXTRA_DIST = http.dox @@ -54,7 +54,7 @@ libkea_http_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la libkea_http_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la libkea_http_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la libkea_http_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la -libkea_http_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) +libkea_http_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS) # If we want to get rid of all generated messages files, we need to use # make maintainer-clean. The proper way to introduce custom commands for diff --git a/src/lib/http/client.cc b/src/lib/http/client.cc index 3958ab6959..affcb985f1 100644 --- a/src/lib/http/client.cc +++ b/src/lib/http/client.cc @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -41,11 +41,11 @@ namespace { /// The part of the HTTP message beyond this value is truncated. constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024; -/// @brief TCP socket callback function type. +/// @brief TCP / TLS socket callback function type. typedef std::function SocketCallbackFunction; -/// @brief Socket callback class required by the TCPSocket API. +/// @brief Socket callback class required by the TCPSocket and TLSSocket APIs. /// /// Its function call operator ignores callbacks invoked with "operation aborted" /// error codes. Such status codes are generated when the posted IO operations @@ -99,7 +99,7 @@ typedef boost::shared_ptr ConnectionPoolPtr; /// the new request is stored in the FIFO queue. The queued requests to the /// particular URL are sent to the server when the current transaction ends. /// -/// The communication over the TCP socket is asynchronous. The caller is +/// The communication over the transport socket is asynchronous. The caller is /// notified about the completion of the transaction via a callback that the /// caller supplies when initiating the transaction. class Connection : public boost::enable_shared_from_this { @@ -108,10 +108,12 @@ public: /// @brief Constructor. /// /// @param io_service IO service to be used for the connection. + /// @param context TLS context to be used for the connection. /// @param conn_pool Back pointer to the connection pool to which this /// connection belongs. /// @param url URL associated with this connection. explicit Connection(IOService& io_service, + const TlsContextPtr& context, const ConnectionPoolPtr& conn_pool, const Url& url); @@ -132,6 +134,8 @@ public: /// transaction completes. /// @param connect_callback Pointer to the callback function to be invoked /// when the client connects to the server. + /// @param handshake_callback Optional callback invoked when the client + /// performs the TLS handshake with the server. /// @param close_callback Pointer to the callback function to be invoked /// when the client closes the socket to the server. void doTransaction(const HttpRequestPtr& request, @@ -139,6 +143,7 @@ public: const long request_timeout, const HttpClient::RequestHandler& callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback); /// @brief Closes the socket and cancels the request timer. @@ -191,6 +196,8 @@ private: /// transaction completes. /// @param connect_callback Pointer to the callback function to be invoked /// when the client connects to the server. + /// @param handshake_callback Optional callback invoked when the client + /// performs the TLS handshake with the server. /// @param close_callback Pointer to the callback function to be invoked /// when the client closes the socket to the server. void doTransactionInternal(const HttpRequestPtr& request, @@ -198,6 +205,7 @@ private: const long request_timeout, const HttpClient::RequestHandler& callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback); /// @brief Closes the socket and cancels the request timer. @@ -281,6 +289,11 @@ private: /// @param request_timeout New timer interval in milliseconds. void scheduleTimer(const long request_timeout); + /// @brief Asynchronously performs the TLS handshake. + /// + /// @param transid Current transaction id. + void doHandshake(const uint64_t transid); + /// @brief Asynchronously sends data over the socket. /// /// The data sent over the socket are stored in the @c buf_. @@ -298,7 +311,8 @@ private: /// @brief Local callback invoked when the connection is established. /// /// If the connection is successfully established, this callback will start - /// to asynchronously send the request over the socket. + /// to asynchronously send the request over the socket or perform the + /// TLS handshake with the server. /// /// @param Pointer to the callback to be invoked when client connects to /// the server. @@ -308,6 +322,19 @@ private: const uint64_t transid, const boost::system::error_code& ec); + /// @brief Local callback invoked when the handshake is performed. + /// + /// If the handshake is successfully performed, this callback will start + /// to asynchronously send the request over the socket. + /// + /// @param Pointer to the callback to be invoked when client performs + /// the TLS handshake with the server. + /// @param transid Current transaction id. + /// @param ec Error code being a result of the connection attempt. + void handshakeCallback(HttpClient::HandshakeHandler handshake_callback, + const uint64_t transid, + const boost::system::error_code& ec); + /// @brief Local callback invoked when an attempt to send a portion of data /// over the socket has ended. /// @@ -355,6 +382,7 @@ private: /// @brief Socket to be used for this connection. TCPSocket socket_; + ////// change this /// @brief Interval timer used for detecting request timeouts. IntervalTimer timer_; @@ -380,7 +408,10 @@ private: /// @brief Identifier of the current transaction. uint64_t current_transid_; - /// @brief User supplied callback. + /// @brief User supplied handshake callback. + HttpClient::HandshakeHandler handshake_callback_; + + /// @brief User supplied close callback. HttpClient::CloseHandler close_callback_; /// @brief Flag to indicate that a transaction is running. @@ -436,6 +467,7 @@ public: /// in progress for the given URL. Otherwise, the request is queued. /// /// @param url Destination where the request should be sent. + /// @param context TLS context to be used for the connection. /// @param request Pointer to the request to be sent to the server. /// @param response Pointer to the object into which the response should be /// stored. @@ -445,24 +477,30 @@ public: /// transaction ends. /// @param connect_callback Pointer to the user callback to be invoked when the /// client connects to the server. + /// @param handshake_callback Optional callback invoked when the client + /// performs the TLS handshake with the server. /// @param close_callback Pointer to the user callback to be invoked when the /// client closes the connection to the server. void queueRequest(const Url& url, + const TlsContextPtr& context, const HttpRequestPtr& request, const HttpResponsePtr& response, const long request_timeout, const HttpClient::RequestHandler& request_callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback) { if (MultiThreadingMgr::instance().getMode()) { std::lock_guard lk(mutex_); - return (queueRequestInternal(url, request, response, + return (queueRequestInternal(url, context, request, response, request_timeout, request_callback, - connect_callback, close_callback)); + connect_callback, handshake_callback, + close_callback)); } else { - return (queueRequestInternal(url, request, response, + return (queueRequestInternal(url, context, request, response, request_timeout, request_callback, - connect_callback, close_callback)); + connect_callback, handshake_callback, + close_callback)); } } @@ -528,8 +566,10 @@ private: RequestDescriptor desc = it->second.front(); it->second.pop(); desc.conn_->doTransaction(desc.request_, desc.response_, - desc.request_timeout_, desc.callback_, + desc.request_timeout_, + desc.callback_, desc.connect_callback_, + desc.handshake_callback_, desc.close_callback_); } } @@ -543,6 +583,7 @@ private: /// This method should be called in a thread safe context. /// /// @param url Destination where the request should be sent. + /// @param context TLS context to be used for the connection. /// @param request Pointer to the request to be sent to the server. /// @param response Pointer to the object into which the response should be /// stored. @@ -552,14 +593,18 @@ private: /// transaction ends. /// @param connect_callback Pointer to the user callback to be invoked when the /// client connects to the server. + /// @param handshake_callback Optional callback invoked when the client + /// performs the TLS handshake with the server. /// @param close_callback Pointer to the user callback to be invoked when the /// client closes the connection to the server. void queueRequestInternal(const Url& url, + const TlsContextPtr& context, const HttpRequestPtr& request, const HttpResponsePtr& response, const long request_timeout, const HttpClient::RequestHandler& request_callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback) { auto it = conns_.find(url); if (it != conns_.end()) { @@ -571,20 +616,24 @@ private: request_timeout, request_callback, connect_callback, + handshake_callback, close_callback)); } else { // Connection is idle, so we can start the transaction. conn->doTransaction(request, response, request_timeout, request_callback, connect_callback, - close_callback); + handshake_callback, close_callback); } } else { // There is no connection with this destination yet. Let's create // it and start the transaction. - ConnectionPtr conn(new Connection(io_service_, shared_from_this(), + ConnectionPtr conn(new Connection(io_service_, + context, + shared_from_this(), url)); - conn->doTransaction(request, response, request_timeout, request_callback, - connect_callback, close_callback); + conn->doTransaction(request, response, request_timeout, + request_callback, connect_callback, + handshake_callback, close_callback); conns_[url] = conn; } } @@ -683,6 +732,8 @@ private: /// @param callback Pointer to the user callback. /// @param connect_callback pointer to the user callback to be invoked /// when the client connects to the server. + /// @param handshake_callback Optional callback invoked when the client + /// performs the TLS handshake with the server. /// @param close_callback pointer to the user callback to be invoked /// when the client closes the connection to the server. RequestDescriptor(const ConnectionPtr& conn, @@ -691,10 +742,12 @@ private: const long& request_timeout, const HttpClient::RequestHandler& callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback) : conn_(conn), request_(request), response_(response), request_timeout_(request_timeout), callback_(callback), connect_callback_(connect_callback), + handshake_callback_(handshake_callback), close_callback_(close_callback) { } @@ -716,6 +769,9 @@ private: /// @brief Holds pointer to the user callback for connect. HttpClient::ConnectHandler connect_callback_; + /// @brief Holds pointer to the user callback for handshake. + HttpClient::HandshakeHandler handshake_callback_; + /// @brief Holds pointer to the user callback for close. HttpClient::CloseHandler close_callback_; }; @@ -728,12 +784,14 @@ private: }; Connection::Connection(IOService& io_service, + const TlsContextPtr& context, const ConnectionPoolPtr& conn_pool, const Url& url) : conn_pool_(conn_pool), url_(url), socket_(io_service), timer_(io_service), current_request_(), current_response_(), parser_(), current_callback_(), buf_(), input_buf_(), current_transid_(0), close_callback_(), started_(false) { + ////// to finish } Connection::~Connection() { @@ -770,14 +828,17 @@ Connection::doTransaction(const HttpRequestPtr& request, const long request_timeout, const HttpClient::RequestHandler& callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback) { if (MultiThreadingMgr::instance().getMode()) { std::lock_guard lk(mutex_); doTransactionInternal(request, response, request_timeout, - callback, connect_callback, close_callback); + callback, connect_callback, handshake_callback, + close_callback); } else { doTransactionInternal(request, response, request_timeout, - callback, connect_callback, close_callback); + callback, connect_callback, handshake_callback, + close_callback); } } @@ -787,6 +848,7 @@ Connection::doTransactionInternal(const HttpRequestPtr& request, const long request_timeout, const HttpClient::RequestHandler& callback, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback) { try { started_ = true; @@ -795,6 +857,7 @@ Connection::doTransactionInternal(const HttpRequestPtr& request, parser_.reset(new HttpResponseParser(*current_response_)); parser_->initModel(); current_callback_ = callback; + handshake_callback_ = handshake_callback; close_callback_ = close_callback; // Starting new transaction. Generate new transaction id. @@ -998,6 +1061,21 @@ Connection::scheduleTimer(const long request_timeout) { } } +void +Connection::doHandshake(const uint64_t transid) { + SocketCallback socket_cb(std::bind(&Connection::handshakeCallback, + shared_from_this(), + handshake_callback_, + transid, + ph::_1)); + try { + ////////socket_.handshake(false, socket_cb); + + } catch (...) { + terminate(boost::asio::error::not_connected); + } +} + void Connection::doSend(const uint64_t transid) { SocketCallback socket_cb(std::bind(&Connection::sendCallback, @@ -1059,6 +1137,36 @@ Connection::connectCallback(HttpClient::ConnectHandler connect_callback, (ec.value() != boost::asio::error::already_connected)) { terminate(ec); + } else { + /////////// insert doHandshake(transid); here + + // Start sending the request asynchronously. + doSend(transid); + } +} + +void +Connection::handshakeCallback(HttpClient::ConnectHandler connect_callback, + const uint64_t transid, + const boost::system::error_code& ec) { + if (checkPrematureTimeout(transid)) { + return; + } + + // Run user defined handshake callback if specified. + if (handshake_callback_) { + // If the user defined callback indicates that the connection + // should not be continued. + if (!handshake_callback_(ec, socket_.getNative())) { + return; + } + } + + if (ec && (ec.value() == boost::asio::error::operation_aborted)) { + return; + } else if (ec) { + terminate(ec); + } else { // Start sending the request asynchronously. doSend(transid); @@ -1219,11 +1327,14 @@ HttpClient::HttpClient(IOService& io_service) } void -HttpClient::asyncSendRequest(const Url& url, const HttpRequestPtr& request, +HttpClient::asyncSendRequest(const Url& url, + const TlsContextPtr& context, + const HttpRequestPtr& request, const HttpResponsePtr& response, const HttpClient::RequestHandler& request_callback, const HttpClient::RequestTimeout& request_timeout, const HttpClient::ConnectHandler& connect_callback, + const HttpClient::HandshakeHandler& handshake_callback, const HttpClient::CloseHandler& close_callback) { if (!url.isValid()) { isc_throw(HttpClientError, "invalid URL specified for the HTTP client"); @@ -1241,8 +1352,10 @@ HttpClient::asyncSendRequest(const Url& url, const HttpRequestPtr& request, isc_throw(HttpClientError, "callback for HTTP transaction must not be null"); } - impl_->conn_pool_->queueRequest(url, request, response, request_timeout.value_, - request_callback, connect_callback, close_callback); + impl_->conn_pool_->queueRequest(url, context, request, response, + request_timeout.value_, + request_callback, connect_callback, + handshake_callback, close_callback); } void diff --git a/src/lib/http/client.h b/src/lib/http/client.h index 9662b796bc..bb4463c4f3 100644 --- a/src/lib/http/client.h +++ b/src/lib/http/client.h @@ -59,11 +59,12 @@ class HttpClientImpl; /// a request by trying to read from the socket (with message peeking). If /// the socket is usable the client uses it to transmit the request. /// -/// This classes exposes the underlying TCP socket's descriptor for each -/// connection via connect and close callbacks. This is done to permit the -/// sockets to be monitored for IO readiness by external code that's something -/// other than boost::asio (e.g.select() or epoll()), and would thus otherwise -/// starve the client's IOService and cause a backlog of ready event handlers. +/// This classes exposes the underlying transport socket's descriptor for +/// each connection via connect, handshake and close callbacks. +/// This is done to permit the sockets to be monitored for IO readiness +/// by external code that's something other than boost::asio +/// (e.g.select() or epoll()), and would thus otherwise starve the +/// client's IOService and cause a backlog of ready event handlers. /// /// All errors are reported to the caller via the callback function supplied /// to the @ref HttpClient::asyncSendRequest. The IO errors are communicated @@ -100,6 +101,14 @@ public: /// so a not null error code does not always mean the connect failed. typedef std::function ConnectHandler; + /// @brief Optional handler invoked when client performs the TLS handshake + /// with the server. + /// + /// Returned boolean value indicates whether the client should continue + /// connecting to the server (if true) or not (false). + /// @note The second argument is not used. + typedef std::function HandshakeHandler; + /// @brief Optional handler invoked when client closes the connection to the server. /// /// It is passed the native socket handler of the connection's TCP socket. @@ -121,12 +130,13 @@ public: /// transaction is started immediately. /// /// The existing connection is tested before it is used for the new - /// transaction by attempting to read (with message peeking) from the open - /// TCP socket. If the read attempt is successful, the client will transmit - /// the HTTP request to the server using this connection. It is possible - /// that the server closes the connection between the connection test and - /// sending the request. In such case, an error will be returned and the - /// caller will need to try re-sending the request. + /// transaction by attempting to read (with message peeking) from + /// the open transport socket. If the read attempt is successful, + /// the client will transmit the HTTP request to the server using + /// this connection. It is possible that the server closes the + /// connection between the connection test and sending the request. + /// In such case, an error will be returned and the caller will + /// need to try re-sending the request. /// /// If the connection test fails, the client will close the socket and /// reconnect to the server prior to sending the request. @@ -166,6 +176,7 @@ public: /// callback can be used to recognize this condition. /// /// @param url URL where the request should be send. + /// @param context TLS context. /// @param request Pointer to the object holding a request. /// @param response Pointer to the object where response should be stored. /// @param request_callback Pointer to the user callback function invoked @@ -173,11 +184,14 @@ public: /// @param request_timeout Timeout for the transaction in milliseconds. /// @param connect_callback Optional callback invoked when the client /// connects to the server. + /// @param handshake_callback Optional callback invoked when the client + /// performs the TLS handshake with the server. /// @param close_callback Optional callback invoked when the client /// closes the connection to the server. /// /// @throw HttpClientError If invalid arguments were provided. void asyncSendRequest(const Url& url, + const asiolink::TlsContextPtr& context, const HttpRequestPtr& request, const HttpResponsePtr& response, const RequestHandler& request_callback, @@ -185,6 +199,8 @@ public: RequestTimeout(10000), const ConnectHandler& connect_callback = ConnectHandler(), + const HandshakeHandler& handshake_callback = + HandshakeHandler(), const CloseHandler& close_callback = CloseHandler()); diff --git a/src/lib/http/connection.cc b/src/lib/http/connection.cc index 7b425e2bf9..db47dce69c 100644 --- a/src/lib/http/connection.cc +++ b/src/lib/http/connection.cc @@ -63,10 +63,12 @@ SocketCallback::operator()(boost::system::error_code ec, size_t length) { } HttpConnection::HttpConnection(asiolink::IOService& io_service, - HttpAcceptor& acceptor, + const HttpAcceptorPtr& acceptor, + const TlsContextPtr& context, HttpConnectionPool& connection_pool, const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback, + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback, const long request_timeout, const long idle_timeout) : request_timer_(io_service), @@ -76,7 +78,8 @@ HttpConnection::HttpConnection(asiolink::IOService& io_service, acceptor_(acceptor), connection_pool_(connection_pool), response_creator_(response_creator), - acceptor_callback_(callback) { + acceptor_callback_(acceptor_callback), + handshake_callback_(handshake_callback) { } HttpConnection::~HttpConnection() { @@ -110,7 +113,7 @@ HttpConnection::asyncAccept() { shared_from_this(), ph::_1); // error try { - acceptor_.asyncAccept(socket_, cb); + acceptor_->asyncAccept(socket_, cb); } catch (const std::exception& ex) { isc_throw(HttpConnectionError, "unable to start accepting TCP " @@ -118,6 +121,23 @@ HttpConnection::asyncAccept() { } } +void +HttpConnection::doHandshake() { + // Create instance of the callback. It is safe to pass the local instance + // of the callback, because the underlying boost functions make copies + // as needed. + SocketCallback cb(std::bind(&HttpConnection::handshakeCallback, + shared_from_this(), + ph::_1)); // error + try { + ////////// socket_.handshake(true, cb); + + } catch (const std::exception& ex) { + isc_throw(HttpConnectionError, "unable to perform TLS handshake: " + << ex.what()); + } +} + void HttpConnection::doRead(TransactionPtr transaction) { try { @@ -193,7 +213,7 @@ HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response, void HttpConnection::acceptorCallback(const boost::system::error_code& ec) { - if (!acceptor_.isOpen()) { + if (!acceptor_->isOpen()) { return; } @@ -206,11 +226,30 @@ HttpConnection::acceptorCallback(const boost::system::error_code& ec) { if (!ec) { LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL, HTTP_REQUEST_RECEIVE_START) + //////////// change the message .arg(getRemoteEndpointAddressAsText()) .arg(static_cast(request_timeout_/1000)); setupRequestTimer(); doRead(); + ////////// or doHandshake(); + } +} + +void +HttpConnection::handshakeCallback(const boost::system::error_code& ec) { + if (ec) { + stopThisConnection(); + } + + handshake_callback_(ec); + + if (!ec) { + LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL, + HTTP_REQUEST_RECEIVE_START) + .arg(getRemoteEndpointAddressAsText()); + + doRead(); } } @@ -427,7 +466,5 @@ HttpConnection::getRemoteEndpointAddressAsText() const { return ("(unknown address)"); } - } // end of namespace isc::http } // end of namespace isc - diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h index fa735064c7..12f752c13e 100644 --- a/src/lib/http/connection.h +++ b/src/lib/http/connection.h @@ -230,21 +230,26 @@ public: /// @brief Constructor. /// /// @param io_service IO service to be used by the connection. - /// @param acceptor Reference to the TCP acceptor object used to listen for + /// @param acceptor Pointer to the TCP acceptor object used to listen for /// new HTTP connections. + /////// add a TLS acceptor + /// @param context TLS context. /// @param connection_pool Connection pool in which this connection is /// stored. /// @param response_creator Pointer to the response creator object used to /// create HTTP response from the HTTP request received. - /// @param callback Callback invoked when new connection is accepted. + /// @param acceptor_callback Callback invoked when new connection is accepted. + /// @param handshake_callback Callback invoked when TLS handshake is performed. /// @param request_timeout Configured timeout for a HTTP request. /// @param idle_timeout Timeout after which persistent HTTP connection is /// closed by the server. HttpConnection(asiolink::IOService& io_service, - HttpAcceptor& acceptor, + const HttpAcceptorPtr& acceptor, + const asiolink::TlsContextPtr& context, HttpConnectionPool& connection_pool, const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback, + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback, const long request_timeout, const long idle_timeout); @@ -256,12 +261,19 @@ public: /// @brief Asynchronously accepts new connection. /// /// When the connection is established successfully, the timeout timer is - /// setup and the asynchronous read from the socket is started. + /// setup and the asynchronous read from the socket or handshake with + /// the client is started. void asyncAccept(); /// @brief Closes the socket. void close(); + /// @brief Asynchronously performs TLS handshake. + /// + /// When the handshake is performed successfully, the asynchronous read + /// from the socket is started. + void doHandshake(); + /// @brief Starts asynchronous read from the socket. /// /// The data received over the socket are supplied to the HTTP parser until @@ -301,11 +313,20 @@ protected: /// /// It invokes external (supplied via constructor) acceptor callback. If /// the acceptor is not opened it returns immediately. If the connection - /// is accepted successfully the @ref HttpConnection::doRead is called. + /// is accepted successfully the @ref HttpConnection::doRead or + /// @ref HttpConnection::doHandshake is called. /// /// @param ec Error code. void acceptorCallback(const boost::system::error_code& ec); + /// @brief Local callback invoked when TLS handshake is performed. + /// + /// If the handshake is performed successfully the @ref + /// HttpConnection::doRead is called. + /// + /// @param ec Error code. + void handshakeCallback(const boost::system::error_code& ec); + /// @brief Callback invoked when new data is received over the socket. /// /// This callback supplies the data to the HTTP parser and continues @@ -360,6 +381,9 @@ protected: /// @brief Configured Request Timeout in milliseconds. long request_timeout_; + /// @brief TLS context. + asiolink::TlsContextPtr context_; + /// @brief Timeout after which the persistent HTTP connection is closed /// by the server. long idle_timeout_; @@ -367,8 +391,9 @@ protected: /// @brief Socket used by this connection. asiolink::TCPSocket socket_; - /// @brief Reference to the TCP acceptor used to accept new connections. - HttpAcceptor& acceptor_; + /// @brief Pointer to the TCP acceptor used to accept new connections. + HttpAcceptorPtr acceptor_; + /////////////// Add a TLS acceptor. /// @brief Connection pool holding this connection. HttpConnectionPool& connection_pool_; @@ -379,6 +404,9 @@ protected: /// @brief External TCP acceptor callback. HttpAcceptorCallback acceptor_callback_; + + /// @brief External TLS handshake callback. + HttpAcceptorCallback handshake_callback_; }; } // end of namespace isc::http diff --git a/src/lib/http/http_acceptor.h b/src/lib/http/http_acceptor.h index 9ef70123b4..26fa0236e9 100644 --- a/src/lib/http/http_acceptor.h +++ b/src/lib/http/http_acceptor.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,7 +7,8 @@ #ifndef HTTP_ACCEPTOR_H #define HTTP_ACCEPTOR_H -#include +#include +#include #include #include @@ -21,6 +22,9 @@ HttpAcceptorCallback; /// @brief Type of the TCP acceptor used in this library. typedef asiolink::TCPAcceptor HttpAcceptor; +/// @brief Type of shared pointer to TCP acceptors. +typedef boost::shared_ptr HttpAcceptorPtr; + } // end of namespace isc::http } // end of namespace isc diff --git a/src/lib/http/http_messages.mes b/src/lib/http/http_messages.mes index 87e7a69e4f..f3af6b9fe4 100644 --- a/src/lib/http/http_messages.mes +++ b/src/lib/http/http_messages.mes @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC") # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -33,6 +33,12 @@ from the server. The first argument specifies an URL of the server. The second argument provides a response in the textual format. The request is truncated by the logger if it is too large to be printed. +% HTTP_CLIENT_HANDSHAKE_START start TLS handshake with %1 with timeout %2 +This debug message is issued when the server starts the TLS handshake +with the remote endpoint. The first argument specifies the address +of the remote endpoint. The second argument specifies request timeout in +seconds. + % HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request authorized for '%1' This information message is issued when the server receives with a matching authentication header. The argument provides the user id. @@ -115,7 +121,7 @@ will be interrupted. New transactions should be conducted normally. This debug message is issued when the server starts receiving new request over the established connection. The first argument specifies the address of the remote endpoint. The second argument specifies request timeout in -seconds. +seconds.TTTTTTTTTOOOOOOOOOOOODDDDDDDDDDOOOOOOOOO % HTTP_SERVER_RESPONSE_RECEIVED received HTTP response from %1 This debug message is issued when the client finished receiving an HTTP diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc index 720570a046..63455f88f5 100644 --- a/src/lib/http/listener.cc +++ b/src/lib/http/listener.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -18,11 +18,13 @@ namespace http { HttpListener::HttpListener(IOService& io_service, const asiolink::IOAddress& server_address, const unsigned short server_port, + const TlsContextPtr& context, const HttpResponseCreatorFactoryPtr& creator_factory, const HttpListener::RequestTimeout& request_timeout, const HttpListener::IdleTimeout& idle_timeout) : impl_(new HttpListenerImpl(io_service, server_address, server_port, - creator_factory, request_timeout.value_, + context, creator_factory, + request_timeout.value_, idle_timeout.value_)) { } diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h index f64d3edd99..6379e632d7 100644 --- a/src/lib/http/listener.h +++ b/src/lib/http/listener.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include #include #include @@ -30,7 +33,7 @@ class HttpListenerImpl; /// @brief HTTP listener. /// /// This class is an entry point to the use of HTTP services in Kea. -/// It creates a TCP acceptor service on the specified address and +/// It creates a transport acceptor service on the specified address and /// port and listens to the incoming HTTP connections. The constructor /// receives a pointer to the implementation of the /// @ref HttpResponseCreatorFactory, which is used by the @ref HttpListener @@ -84,6 +87,7 @@ public: /// @param io_service IO service to be used by the listener. /// @param server_address Address on which the HTTP service should run. /// @param server_port Port number on which the HTTP service should run. + /// @param context TLS context. /// @param creator_factory Pointer to the caller-defined /// @ref HttpResponseCreatorFactory derivation which should be used to /// create @ref HttpResponseCreator instances. @@ -97,13 +101,14 @@ public: HttpListener(asiolink::IOService& io_service, const asiolink::IOAddress& server_address, const unsigned short server_port, + const asiolink::TlsContextPtr& context, const HttpResponseCreatorFactoryPtr& creator_factory, const RequestTimeout& request_timeout, const IdleTimeout& idle_timeout); /// @brief Destructor. /// - /// Stops all active connections and closes TCP acceptor service. + /// Stops all active connections and closes transport acceptor service. ~HttpListener(); /// @brief Returns local address on which server is listening. diff --git a/src/lib/http/listener_impl.cc b/src/lib/http/listener_impl.cc index 1a80f6817f..d401d4dac2 100644 --- a/src/lib/http/listener_impl.cc +++ b/src/lib/http/listener_impl.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -19,10 +19,13 @@ namespace http { HttpListenerImpl::HttpListenerImpl(IOService& io_service, const asiolink::IOAddress& server_address, const unsigned short server_port, + const TlsContextPtr& context, const HttpResponseCreatorFactoryPtr& creator_factory, const long request_timeout, const long idle_timeout) - : io_service_(io_service), acceptor_(io_service), + : io_service_(io_service), context_(context), + ///////// move acceptor init below + acceptor_(new HttpAcceptor(io_service)), endpoint_(), connections_(), creator_factory_(creator_factory), request_timeout_(request_timeout), idle_timeout_(idle_timeout) { @@ -62,10 +65,10 @@ HttpListenerImpl::getEndpoint() const { void HttpListenerImpl::start() { try { - acceptor_.open(*endpoint_); - acceptor_.setOption(HttpAcceptor::ReuseAddress(true)); - acceptor_.bind(*endpoint_); - acceptor_.listen(); + acceptor_->open(*endpoint_); + acceptor_->setOption(HttpAcceptor::ReuseAddress(true)); + acceptor_->bind(*endpoint_); + acceptor_->listen(); } catch (const boost::system::system_error& ex) { stop(); @@ -79,7 +82,7 @@ HttpListenerImpl::start() { void HttpListenerImpl::stop() { connections_.stopAll(); - acceptor_.close(); + acceptor_->close(); } void @@ -90,8 +93,11 @@ HttpListenerImpl::accept() { HttpResponseCreatorPtr response_creator = creator_factory_->create(); HttpAcceptorCallback acceptor_callback = std::bind(&HttpListenerImpl::acceptHandler, this, ph::_1); + HttpAcceptorCallback handshake_callback = + std::bind(&HttpListenerImpl::handshakeHandler, this, ph::_1); HttpConnectionPtr conn = createConnection(response_creator, - acceptor_callback); + acceptor_callback, + handshake_callback); // Add this new connection to the pool. connections_.start(conn); } @@ -103,12 +109,20 @@ HttpListenerImpl::acceptHandler(const boost::system::error_code&) { accept(); } +void +HttpListenerImpl::handshakeHandler(const boost::system::error_code&) { + // The TLS handshake has been performed. + ///////////// DO MORE!!!!!!!! +} + HttpConnectionPtr HttpListenerImpl::createConnection(const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback) { + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback) { HttpConnectionPtr - conn(new HttpConnection(io_service_, acceptor_, connections_, - response_creator, callback, + conn(new HttpConnection(io_service_, acceptor_, context_, + connections_, response_creator, + acceptor_callback, handshake_callback, request_timeout_, idle_timeout_)); return (conn); } diff --git a/src/lib/http/listener_impl.h b/src/lib/http/listener_impl.h index 2ae81037e1..26e54fbb3c 100644 --- a/src/lib/http/listener_impl.h +++ b/src/lib/http/listener_impl.h @@ -1,4 +1,4 @@ -// Copyright (C) 2019,2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -33,6 +33,7 @@ public: /// @param io_service IO service to be used by the listener. /// @param server_address Address on which the HTTP service should run. /// @param server_port Port number on which the HTTP service should run. + /// @param context TLS context. /// @param creator_factory Pointer to the caller-defined /// @ref HttpResponseCreatorFactory derivation which should be used to /// create @ref HttpResponseCreator instances. @@ -46,6 +47,7 @@ public: HttpListenerImpl(asiolink::IOService& io_service, const asiolink::IOAddress& server_address, const unsigned short server_port, + const asiolink::TlsContextPtr& context, const HttpResponseCreatorFactoryPtr& creator_factory, const long request_timeout, const long idle_timeout); @@ -87,6 +89,11 @@ protected: /// @param ec Error code passed to the handler. This is currently ignored. void acceptHandler(const boost::system::error_code& ec); + /// @brief Callback invoked when the TLS handshake is performed. + /// + /// @param ec Error code passed to the handler. This is currently ignored. + void handshakeHandler(const boost::system::error_code& ec); + /// @brief Creates an instance of the @c HttpConnection. /// /// This method is virtual so as it can be overridden when customized @@ -94,17 +101,23 @@ protected: /// /// @param response_creator Pointer to the response creator object used to /// create HTTP response from the HTTP request received. - /// @param callback Callback invoked when new connection is accepted. + /// @param acceptor_callback Callback invoked when new connection is accepted. + /// @param handshake_callback Callback invoked when TLS handshake is performed. /// /// @return Pointer to the created connection. virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback); + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback); /// @brief Reference to the IO service. asiolink::IOService& io_service_; + /// @brief TLS context. + asiolink::TlsContextPtr context_; + /// @brief Acceptor instance. - HttpAcceptor acceptor_; + HttpAcceptorPtr acceptor_; + /////////// Fork it? /// @brief Pointer to the endpoint representing IP address and port on /// which the service is running. diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am index 2dc175f237..2e97f269fb 100644 --- a/src/lib/http/tests/Makefile.am +++ b/src/lib/http/tests/Makefile.am @@ -1,7 +1,8 @@ SUBDIRS = . AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) +AM_CPPFLAGS += -DTEST_CA_DIR=\"$(srcdir)/../../asiolink/tests/ca\" AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/http/tests\" AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" @@ -42,7 +43,7 @@ libhttp_unittests_SOURCES += test_http_client.h libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS) -libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) +libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) libhttp_unittests_LDADD = $(top_builddir)/src/lib/http/libkea-http.la libhttp_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la @@ -53,7 +54,7 @@ libhttp_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la libhttp_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la libhttp_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la libhttp_unittests_LDADD += $(LOG4CPLUS_LIBS) -libhttp_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD) +libhttp_unittests_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) $(GTEST_LDADD) endif noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/http/tests/connection_pool_unittests.cc b/src/lib/http/tests/connection_pool_unittests.cc index 0635f2a9bd..18da6401c4 100644 --- a/src/lib/http/tests/connection_pool_unittests.cc +++ b/src/lib/http/tests/connection_pool_unittests.cc @@ -105,7 +105,9 @@ public: /// @brief Constructor. HttpConnectionPoolTest() - : io_service_(), acceptor_(io_service_), connection_pool_(), + : io_service_(), + acceptor_(new HttpAcceptor(io_service_)), + connection_pool_(), response_creator_(new TestHttpResponseCreator()) { MultiThreadingMgr::instance().setMode(false); } @@ -119,16 +121,20 @@ public: void startStopTest() { // Create two distinct connections. HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_, + TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), + HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_, + TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), + HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); // The pool should be initially empty. @@ -162,16 +168,20 @@ public: void stopAllTest() { // Create two distinct connections. HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_, + TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), + HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_, + TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), + HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); TestHttpConnectionPool pool; @@ -189,16 +199,20 @@ public: /// @brief Verifies that stopping a non-existing connection is no-op. void stopInvalidTest() { HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_, + TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), + HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_, + TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), + HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); TestHttpConnectionPool pool; @@ -209,7 +223,7 @@ public: } IOService io_service_; ///< IO service. - HttpAcceptor acceptor_; ///< Test acceptor. + HttpAcceptorPtr acceptor_; ///< Test acceptor. HttpConnectionPool connection_pool_; ///< Test connection pool. HttpResponseCreatorPtr response_creator_; ///< Test response creator. diff --git a/src/lib/http/tests/server_client_unittests.cc b/src/lib/http/tests/server_client_unittests.cc index f6cf94869b..48316b9c36 100644 --- a/src/lib/http/tests/server_client_unittests.cc +++ b/src/lib/http/tests/server_client_unittests.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -210,11 +211,12 @@ public: HttpListenerImplCustom(IOService& io_service, const IOAddress& server_address, const unsigned short server_port, + const TlsContextPtr& context, const HttpResponseCreatorFactoryPtr& creator_factory, const long request_timeout, const long idle_timeout) : HttpListenerImpl(io_service, server_address, server_port, - creator_factory, request_timeout, + context, creator_factory, request_timeout, idle_timeout) { } @@ -226,23 +228,28 @@ protected: /// connections are to be used, e.g. in case of unit testing. /// /// @param io_service IO service to be used by the connection. - /// @param acceptor Reference to the TCP acceptor object used to listen for + /// @param acceptor Pointer to the TCP acceptor object used to listen for /// new HTTP connections. + /// @param context TLS context. /// @param connection_pool Connection pool in which this connection is /// stored. /// @param response_creator Pointer to the response creator object used to /// create HTTP response from the HTTP request received. - /// @param callback Callback invoked when new connection is accepted. + /// @param acceptor_callback Callback invoked when new connection is accepted. + /// @param handshake_callback Callback invoked when TLS handshake is performed. /// @param request_timeout Configured timeout for a HTTP request. /// @param idle_timeout Timeout after which persistent HTTP connection is /// closed by the server. /// /// @return Pointer to the created connection. virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback) { + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback) { HttpConnectionPtr - conn(new HttpConnectionType(io_service_, acceptor_, connections_, - response_creator, callback, + conn(new HttpConnectionType(io_service_, acceptor_, + context_, connections_, + response_creator, + acceptor_callback, handshake_callback, request_timeout_, idle_timeout_)); return (conn); } @@ -264,6 +271,7 @@ public: /// @param io_service IO service to be used by the listener. /// @param server_address Address on which the HTTP service should run. /// @param server_port Port number on which the HTTP service should run. + /// @param context TLS context. /// @param creator_factory Pointer to the caller-defined /// @ref HttpResponseCreatorFactory derivation which should be used to /// create @ref HttpResponseCreator instances. @@ -277,16 +285,18 @@ public: HttpListenerCustom(IOService& io_service, const IOAddress& server_address, const unsigned short server_port, + const TlsContextPtr& context, const HttpResponseCreatorFactoryPtr& creator_factory, const HttpListener::RequestTimeout& request_timeout, const HttpListener::IdleTimeout& idle_timeout) - : HttpListener(io_service, server_address, server_port, creator_factory, + : HttpListener(io_service, server_address, server_port, + context, creator_factory, request_timeout, idle_timeout) { // Replace the default implementation with the customized version // using the custom derivation of the HttpConnection. impl_.reset(new HttpListenerImplCustom (io_service, server_address, server_port, - creator_factory, request_timeout.value_, + context, creator_factory, request_timeout.value_, idle_timeout.value_)); } }; @@ -299,25 +309,30 @@ public: /// @brief Constructor. /// /// @param io_service IO service to be used by the connection. - /// @param acceptor Reference to the TCP acceptor object used to listen for + /// @param acceptor Pointer to the TCP acceptor object used to listen for /// new HTTP connections. + /// @param context TLS context. /// @param connection_pool Connection pool in which this connection is /// stored. /// @param response_creator Pointer to the response creator object used to /// create HTTP response from the HTTP request received. - /// @param callback Callback invoked when new connection is accepted. + /// @param acceptor_callback Callback invoked when new connection is accepted. + /// @param handshake_callback Callback invoked when TLS handshake is performed. /// @param request_timeout Configured timeout for a HTTP request. /// @param idle_timeout Timeout after which persistent HTTP connection is /// closed by the server. HttpConnectionLongWriteBuffer(IOService& io_service, - HttpAcceptor& acceptor, + const HttpAcceptorPtr& acceptor, + const TlsContextPtr& context, HttpConnectionPool& connection_pool, const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback, + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback, const long request_timeout, const long idle_timeout) - : HttpConnection(io_service, acceptor, connection_pool, - response_creator, callback, request_timeout, + : HttpConnection(io_service, acceptor, context, connection_pool, + response_creator, acceptor_callback, + handshake_callback, request_timeout, idle_timeout) { } @@ -344,25 +359,30 @@ public: /// @brief Constructor. /// /// @param io_service IO service to be used by the connection. - /// @param acceptor Reference to the TCP acceptor object used to listen for + /// @param acceptor Pointer to the TCP acceptor object used to listen for /// new HTTP connections. + /// @param context TLS context. /// @param connection_pool Connection pool in which this connection is /// stored. /// @param response_creator Pointer to the response creator object used to /// create HTTP response from the HTTP request received. - /// @param callback Callback invoked when new connection is accepted. + /// @param acceptor_callback Callback invoked when new connection is accepted. + /// @param handshake_callback Callback invoked when TLS handshake is performed. /// @param request_timeout Configured timeout for a HTTP request. /// @param idle_timeout Timeout after which persistent HTTP connection is /// closed by the server. HttpConnectionTransactionChange(IOService& io_service, - HttpAcceptor& acceptor, + const HttpAcceptorPtr& acceptor, + const TlsContextPtr& context, HttpConnectionPool& connection_pool, const HttpResponseCreatorPtr& response_creator, - const HttpAcceptorCallback& callback, + const HttpAcceptorCallback& acceptor_callback, + const HttpAcceptorCallback& handshake_callback, const long request_timeout, const long idle_timeout) - : HttpConnection(io_service, acceptor, connection_pool, - response_creator, callback, request_timeout, + : HttpConnection(io_service, acceptor, context, connection_pool, + response_creator, acceptor_callback, + handshake_callback, request_timeout, idle_timeout) { } @@ -480,7 +500,9 @@ public: const HttpVersion& expected_version) { // Open the listener with the Request Timeout of 1 sec and post the // partial request. - HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, + TlsContextPtr context; + HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), + SERVER_PORT, context, factory_, HttpListener::RequestTimeout(1000), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); @@ -531,9 +553,11 @@ public: "{ }"; // Use custom listener and the specialized connection object. + TlsContextPtr context; HttpListenerCustom listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); @@ -575,8 +599,10 @@ TEST_F(HttpListenerTest, listen) { "Content-Length: 3\r\n\r\n" "{ }"; + TlsContextPtr context; HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText()); @@ -605,8 +631,10 @@ TEST_F(HttpListenerTest, keepAlive) { "Connection: Keep-Alive\r\n\r\n" "{ }"; + TlsContextPtr context; HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); @@ -653,8 +681,10 @@ TEST_F(HttpListenerTest, persistentConnection) { "Content-Length: 3\r\n\r\n" "{ }"; + TlsContextPtr context; HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); @@ -703,9 +733,11 @@ TEST_F(HttpListenerTest, keepAliveTimeout) { "Connection: Keep-Alive\r\n\r\n" "{ }"; + TlsContextPtr context; // Specify the idle timeout of 500ms. HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(500)); ASSERT_NO_THROW(listener.start()); @@ -759,9 +791,11 @@ TEST_F(HttpListenerTest, persistentConnectionTimeout) { "Content-Length: 3\r\n\r\n" "{ }"; + TlsContextPtr context; // Specify the idle timeout of 500ms. HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(500)); ASSERT_NO_THROW(listener.start()); @@ -815,8 +849,10 @@ TEST_F(HttpListenerTest, persistentConnectionBadBody) { "Content-Length: 12\r\n\r\n" "{ \"a\": abc }"; + TlsContextPtr context; HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); @@ -859,8 +895,10 @@ TEST_F(HttpListenerTest, persistentConnectionBadBody) { // This test verifies that the HTTP listener can't be started twice. TEST_F(HttpListenerTest, startTwice) { + TlsContextPtr context; HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); EXPECT_THROW(listener.start(), HttpListenerError); @@ -875,8 +913,10 @@ TEST_F(HttpListenerTest, badRequest) { "Content-Length: 3\r\n\r\n" "{ }"; + TlsContextPtr context; HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + context, factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); ASSERT_NO_THROW(listener.start()); ASSERT_NO_THROW(startRequest(request)); @@ -896,8 +936,10 @@ TEST_F(HttpListenerTest, badRequest) { // This test verifies that NULL pointer can't be specified for the // HttpResponseCreatorFactory. TEST_F(HttpListenerTest, invalidFactory) { + TlsContextPtr context; EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS), - SERVER_PORT, HttpResponseCreatorFactoryPtr(), + SERVER_PORT, context, + HttpResponseCreatorFactoryPtr(), HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)), HttpListenerError); @@ -906,8 +948,10 @@ TEST_F(HttpListenerTest, invalidFactory) { // This test verifies that the timeout of 0 can't be specified for the // Request Timeout. TEST_F(HttpListenerTest, invalidRequestTimeout) { + TlsContextPtr context; EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS), - SERVER_PORT, factory_, HttpListener::RequestTimeout(0), + SERVER_PORT, context, factory_, + HttpListener::RequestTimeout(0), HttpListener::IdleTimeout(IDLE_TIMEOUT)), HttpListenerError); } @@ -915,8 +959,9 @@ TEST_F(HttpListenerTest, invalidRequestTimeout) { // This test verifies that the timeout of 0 can't be specified for the // idle persistent connection timeout. TEST_F(HttpListenerTest, invalidIdleTimeout) { + TlsContextPtr context; EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS), - SERVER_PORT, factory_, + SERVER_PORT, context, factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(0)), HttpListenerError); @@ -933,10 +978,11 @@ TEST_F(HttpListenerTest, addressInUse) { acceptor.open(endpoint.protocol()); acceptor.bind(endpoint); + TlsContextPtr context; // Listener should report an error when we try to start it because another // acceptor is bound to that port and address. HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), - SERVER_PORT + 1, factory_, + SERVER_PORT + 1, context, factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)); EXPECT_THROW(listener.start(), HttpListenerError); @@ -989,13 +1035,16 @@ public: HttpClientTest() : HttpListenerTest(), listener_(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + TlsContextPtr(), factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)), listener2_(io_service_, IOAddress(IPV6_SERVER_ADDRESS), SERVER_PORT + 1, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + TlsContextPtr(), factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(IDLE_TIMEOUT)), listener3_(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT + 2, - factory_, HttpListener::RequestTimeout(REQUEST_TIMEOUT), + TlsContextPtr(), factory_, + HttpListener::RequestTimeout(REQUEST_TIMEOUT), HttpListener::IdleTimeout(SHORT_IDLE_TIMEOUT)) { MultiThreadingMgr::instance().setMode(false); } @@ -1050,10 +1099,12 @@ public: Url url("http://127.0.0.1:18123"); // Initiate request to the server. + TlsContextPtr context1; PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version); HttpResponseJsonPtr response1(new HttpResponseJson()); unsigned resp_num = 0; - ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url, context1, + request1, response1, [this, &resp_num](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1064,9 +1115,11 @@ public: })); // Initiate another request to the destination. + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version); HttpResponseJsonPtr response2(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request2, response2, + ASSERT_NO_THROW(client.asyncSendRequest(url, context2, + request2, response2, [this, &resp_num](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1109,10 +1162,12 @@ public: Url url2("http://[::1]:18124"); // Create a request to the first server. + TlsContextPtr context1; PostHttpRequestJsonPtr request1 = createRequest("sequence", 1); HttpResponseJsonPtr response1(new HttpResponseJson()); unsigned resp_num = 0; - ASSERT_NO_THROW(client.asyncSendRequest(url1, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url1, context1, + request1, response1, [this, &resp_num](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1123,9 +1178,11 @@ public: })); // Create a request to the second server. + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 2); HttpResponseJsonPtr response2(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url2, request2, response2, + ASSERT_NO_THROW(client.asyncSendRequest(url2, context2, + request2, response2, [this, &resp_num](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1163,9 +1220,11 @@ public: Url url("http://127.0.0.1:18125"); // Create the first request. + TlsContextPtr context1; PostHttpRequestJsonPtr request1 = createRequest("sequence", 1); HttpResponseJsonPtr response1(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url, context1, + request1, response1, [this](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { io_service_.stop(); @@ -1185,9 +1244,11 @@ public: ASSERT_NO_THROW(runIOService(SHORT_IDLE_TIMEOUT * 2)); // Create another request. + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 2); HttpResponseJsonPtr response2(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request2, response2, + ASSERT_NO_THROW(client.asyncSendRequest(url, context2, + request2, response2, [this](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { io_service_.stop(); @@ -1216,9 +1277,11 @@ public: Url url("http://127.0.0.1:18123"); // Create the request. + TlsContextPtr context; PostHttpRequestJsonPtr request = createRequest("sequence", 1); HttpResponseJsonPtr response(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request, response, + ASSERT_NO_THROW(client.asyncSendRequest(url, context, + request, response, [this](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1243,6 +1306,7 @@ public: // Specify the URL of the server. Url url("http://127.0.0.1:18123"); + TlsContextPtr context; // The response is going to be malformed in such a way that it holds // an invalid content type. We affect the content type by creating // a request that holds a JSON parameter requesting a specific @@ -1250,7 +1314,8 @@ public: PostHttpRequestJsonPtr request = createRequest("requested-content-type", "text/html"); HttpResponseJsonPtr response(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request, response, + ASSERT_NO_THROW(client.asyncSendRequest(url, context, + request, response, [this](const boost::system::error_code& ec, const HttpResponsePtr& response, const std::string& parsing_error) { @@ -1281,6 +1346,7 @@ public: unsigned cb_num = 0; + TlsContextPtr context1; // Create the request which asks the server to generate a partial // (although well formed) response. The client will be waiting for the // rest of the response to be provided and will eventually time out. @@ -1289,7 +1355,8 @@ public: // This value will be set to true if the connection close callback is // invoked upon time out. auto connection_closed = false; - ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url, context1, + request1, response1, [this, &cb_num](const boost::system::error_code& ec, const HttpResponsePtr& response, const std::string&) { @@ -1302,7 +1369,10 @@ public: EXPECT_TRUE(ec.value() == boost::asio::error::timed_out); // There should be no response returned. EXPECT_FALSE(response); - }, HttpClient::RequestTimeout(100), HttpClient::ConnectHandler(), + }, + HttpClient::RequestTimeout(100), + HttpClient::ConnectHandler(), + HttpClient::HandshakeHandler(), [&connection_closed](const int) { // This callback is called when the connection gets closed // by the client. @@ -1311,16 +1381,18 @@ public: ); // Create another request after the timeout. It should be handled ok. + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 1); HttpResponseJsonPtr response2(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request2, response2, - [this, &cb_num](const boost::system::error_code& /*ec*/, - const HttpResponsePtr&, - const std::string&) { - if (++cb_num > 1) { - io_service_.stop(); - } - })); + ASSERT_NO_THROW(client.asyncSendRequest(url, context2, + request2, response2, + [this, &cb_num](const boost::system::error_code& /*ec*/, + const HttpResponsePtr&, + const std::string&) { + if (++cb_num > 1) { + io_service_.stop(); + } + })); // Actually trigger the requests. ASSERT_NO_THROW(runIOService()); @@ -1341,9 +1413,11 @@ public: unsigned cb_num = 0; + TlsContextPtr context; PostHttpRequestJsonPtr request = createRequest("sequence", 1); HttpResponseJsonPtr response(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request, response, + ASSERT_NO_THROW(client.asyncSendRequest(url, context, + request, response, [this, &cb_num](const boost::system::error_code& ec, const HttpResponsePtr& response, const std::string&) { @@ -1369,14 +1443,15 @@ public: })); // Create another request after the timeout. It should be handled ok. - ASSERT_NO_THROW(client.asyncSendRequest(url, request, response, - [this, &cb_num](const boost::system::error_code& /*ec*/, - const HttpResponsePtr&, - const std::string&) { - if (++cb_num > 1) { - io_service_.stop(); - } - })); + ASSERT_NO_THROW(client.asyncSendRequest(url, context, + request, response, + [this, &cb_num](const boost::system::error_code& /*ec*/, + const HttpResponsePtr&, + const std::string&) { + if (++cb_num > 1) { + io_service_.stop(); + } + })); // Actually trigger the requests. ASSERT_NO_THROW(runIOService()); @@ -1412,12 +1487,14 @@ public: Url url("http://127.0.0.1:18123"); // Generate first request. + TlsContextPtr context1; PostHttpRequestJsonPtr request1 = createRequest("sequence", 1); HttpResponseJsonPtr response1(new HttpResponseJson()); // Use very short timeout to make sure that it occurs before we actually // run the transaction. - ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url, context1, + request1, response1, [](const boost::system::error_code& ec, const HttpResponsePtr& response, const std::string&) { @@ -1431,9 +1508,11 @@ public: }, HttpClient::RequestTimeout(1))); if (queue_two_requests) { + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 2); HttpResponseJsonPtr response2(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request2, response2, + ASSERT_NO_THROW(client.asyncSendRequest(url, context2, + request2, response2, [](const boost::system::error_code& ec, const HttpResponsePtr& response, const std::string&) { @@ -1456,9 +1535,11 @@ public: // Now try to send another request to make sure that the client // is healthy. + TlsContextPtr context3; PostHttpRequestJsonPtr request3 = createRequest("sequence", 3); HttpResponseJsonPtr response3(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request3, response3, + ASSERT_NO_THROW(client.asyncSendRequest(url, context3, + request3, response3, [this](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1487,12 +1568,14 @@ public: Url url("http://127.0.0.1:18123"); // Initiate request to the server. + TlsContextPtr context1; PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version); HttpResponseJsonPtr response1(new HttpResponseJson()); unsigned resp_num = 0; ExternalMonitor monitor; - ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url, context1, + request1, response1, [this, &resp_num](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1504,13 +1587,16 @@ public: }, HttpClient::RequestTimeout(10000), std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2), + std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2), std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1) )); // Initiate another request to the destination. + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version); HttpResponseJsonPtr response2(new HttpResponseJson()); - ASSERT_NO_THROW(client.asyncSendRequest(url, request2, response2, + ASSERT_NO_THROW(client.asyncSendRequest(url, context2, + request2, response2, [this, &resp_num](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1521,6 +1607,7 @@ public: }, HttpClient::RequestTimeout(10000), std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2), + std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2), std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1) )); @@ -1578,12 +1665,14 @@ public: Url url("http://127.0.0.1:18123"); // Initiate request to the server. + TlsContextPtr context1; PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version); HttpResponseJsonPtr response1(new HttpResponseJson()); unsigned resp_num = 0; ExternalMonitor monitor; - ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1, + ASSERT_NO_THROW(client.asyncSendRequest(url, context1, + request1, response1, [this, &client, &resp_num, &monitor](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1608,6 +1697,7 @@ public: }, HttpClient::RequestTimeout(10000), std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2), + std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2), std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1) )); @@ -1638,10 +1728,12 @@ public: // Now let's do another request to the destination to verify that // we'll reopen the connection without issue. + TlsContextPtr context2; PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version); HttpResponseJsonPtr response2(new HttpResponseJson()); resp_num = 0; - ASSERT_NO_THROW(client.asyncSendRequest(url, request2, response2, + ASSERT_NO_THROW(client.asyncSendRequest(url, context2, + request2, response2, [this, &client, &resp_num, &monitor](const boost::system::error_code& ec, const HttpResponsePtr&, const std::string&) { @@ -1666,6 +1758,7 @@ public: }, HttpClient::RequestTimeout(10000), std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2), + std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2), std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1) )); @@ -1698,7 +1791,9 @@ public: public: /// @brief Constructor ExternalMonitor() - : registered_fd_(-1), connect_cnt_(0), close_cnt_(0) {}; + : registered_fd_(-1), connect_cnt_(0), handshake_cnt_(0), + close_cnt_(0) { + } /// @brief Connect callback handler /// @param ec Error status of the ASIO connect @@ -1719,6 +1814,15 @@ public: return (true); } + /// @brief Handshake callback handler + /// @param ec Error status of the ASIO connect + bool handshakeHandler(const boost::system::error_code& ec, int) { + ++handshake_cnt_; + // ec indicates an error, return true, so that error can be handled + // by Connection logic. + return (true); + } + /// @brief Close callback handler /// /// @param tcp_native_fd socket descriptor to register @@ -1736,6 +1840,9 @@ public: /// @brief Tracks how many times the connect callback is invoked. int connect_cnt_; + /// @brief Tracks how many times the handshake callback is invoked. + int handshake_cnt_; + /// @brief Tracks how many times the close callback is invoked. int close_cnt_; };