Added first draft of TcpListener classes.
They compile but don't do anything useful.
configure.ac
src/lib/Makefile.am
added tcp
src/lib/tcp/README
src/lib/tcp/tcp_connection.cc
src/lib/tcp/tcp_connection.h
src/lib/tcp/tcp_connection_acceptor.h
src/lib/tcp/tcp_connection_pool.cc
src/lib/tcp/tcp_connection_pool.h
src/lib/tcp/tcp_listener.cc
src/lib/tcp/tcp_listener.h
src/lib/tcp/tcp_log.cc
src/lib/tcp/tcp_log.h
src/lib/tcp/tcp_messages.cc
src/lib/tcp/tcp_messages.h
src/lib/tcp/tcp_messages.mes
src/lib/tcp/tests/.gitignore
src/lib/tcp/tests/Makefile.am
src/lib/tcp/tests/run_unittests.cc
- new files
AC_CONFIG_FILES([src/lib/stats/Makefile])
AC_CONFIG_FILES([src/lib/stats/tests/Makefile])
AC_CONFIG_FILES([src/lib/stats/testutils/Makefile])
+AC_CONFIG_FILES([src/lib/tcp/Makefile])
+AC_CONFIG_FILES([src/lib/tcp/tests/Makefile])
AC_CONFIG_FILES([src/lib/testutils/Makefile])
AC_CONFIG_FILES([src/lib/testutils/dhcp_test_lib.sh],
[chmod +x src/lib/testutils/dhcp_test_lib.sh])
SUBDIRS += pgsql
endif
-SUBDIRS += config_backend hooks dhcp http config stats
+SUBDIRS += config_backend hooks dhcp tcp http config stats
if HAVE_NETCONF
SUBDIRS += yang
--- /dev/null
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = # tcp.dox
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST += tcp_messages.mes
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-tcp.la
+
+libkea_tcp_la_SOURCES = tcp_connection.cc tcp_connection.h
+libkea_tcp_la_SOURCES += tcp_connection_pool.cc tcp_connection_pool.h
+libkea_tcp_la_SOURCES += tcp_listener.cc tcp_listener.h
+libkea_tcp_la_SOURCES += tcp_log.cc tcp_log.h
+libkea_tcp_la_SOURCES += tcp_messages.cc tcp_messages.h
+
+libkea_tcp_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_tcp_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_tcp_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_tcp_la_LDFLAGS += -no-undefined -version-info 52:0:0
+
+libkea_tcp_la_LIBADD =
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_tcp_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
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f tcp_messages.h tcp_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: tcp_messages.h tcp_messages.cc
+ @echo Message files regenerated
+
+tcp_messages.h tcp_messages.cc: tcp_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/tcp/tcp_messages.mes
+
+else
+
+messages tcp_messages.h tcp_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_tcp_includedir = $(pkgincludedir)/tcp
+libkea_tcp_include_HEADERS = \
+ tcp_messages.h \
+ tcp_connection.h \
+ tcp_connection_pool.h \
+ tcp_listener.h \
+ tcp_log.h
--- /dev/null
+The tcp library is intended to provide support for TCP server/listeners.
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#if 0
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <tcp/tcp_connection.h>
+#include <tcp/tcp_connection_pool.h>
+#include <tcp/tcp_log.h>
+#include <tcp/tcp_messages.h>
+#include <boost/make_shared.hpp>
+#include <functional>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the HTTP message that can be logged.
+///
+/// The part of the HTTP message beyond this value is truncated.
+constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
+
+}
+
+namespace isc {
+namespace http {
+
+void
+TcpConnection::
+SocketCallback::operator()(boost::system::error_code ec, size_t length) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+}
+
+TcpConnection::TcpConnection(asiolink::IOService& io_service,
+ const TcpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ TcpConnectionPool& connection_pool,
+ const TcpResponseCreatorPtr& response_creator,
+ const TcpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : request_timer_(io_service),
+ request_timeout_(request_timeout),
+ tls_context_(tls_context),
+ idle_timeout_(idle_timeout),
+ tcp_socket_(),
+ tls_socket_(),
+ acceptor_(acceptor),
+ connection_pool_(connection_pool),
+ response_creator_(response_creator),
+ acceptor_callback_(callback) {
+ if (!tls_context) {
+ tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ }
+}
+
+TcpConnection::~TcpConnection() {
+ close();
+}
+
+void
+TcpConnection::recordParameters(const TcpRequestPtr& request) const {
+ if (!request) {
+ // Should never happen.
+ return;
+ }
+
+ // Record the remote address.
+ request->setRemote(getRemoteEndpointAddressAsText());
+
+ // Record TLS parameters.
+ if (!tls_socket_) {
+ return;
+ }
+
+ // The connection uses HTTPS aka HTTP over TLS.
+ request->setTls(true);
+
+ // Record the first commonName of the subjectName of the client
+ // certificate when wanted.
+ if (TcpRequest::recordSubject_) {
+ request->setSubject(tls_socket_->getTlsStream().getSubject());
+ }
+
+ // Record the first commonName of the issuerName of the client
+ // certificate when wanted.
+ if (TcpRequest::recordIssuer_) {
+ request->setIssuer(tls_socket_->getTlsStream().getIssuer());
+ }
+}
+
+void
+TcpConnection::shutdownCallback(const boost::system::error_code&) {
+ tls_socket_->close();
+}
+
+void
+TcpConnection::shutdown() {
+ request_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+ if (tls_socket_) {
+ // Create instance of the callback to close the socket.
+ SocketCallback cb(std::bind(&TcpConnection::shutdownCallback,
+ shared_from_this(),
+ ph::_1)); // error_code
+ tls_socket_->shutdown(cb);
+ return;
+ }
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to shutdown the socket");
+}
+
+void
+TcpConnection::close() {
+ request_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->close();
+ return;
+ }
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to close the socket");
+}
+
+void
+TcpConnection::shutdownConnection() {
+ try {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_CONNECTION_SHUTDOWN)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.shutdown(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(asiolink_logger, TCP_CONNECTION_SHUTDOWN_FAILED);
+ }
+}
+
+void
+TcpConnection::stopThisConnection() {
+ try {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_CONNECTION_STOP)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.stop(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(asiolink_logger, TCP_CONNECTION_STOP_FAILED);
+ }
+}
+
+void
+TcpConnection::asyncAccept() {
+ // 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.
+ TcpAcceptorCallback cb = std::bind(&TcpConnection::acceptorCallback,
+ shared_from_this(),
+ ph::_1); // error
+ try {
+ TcpsAcceptorPtr tls_acceptor =
+ boost::dynamic_pointer_cast<TcpsAcceptor>(acceptor_);
+ if (!tls_acceptor) {
+ if (!tcp_socket_) {
+ isc_throw(Unexpected, "internal error: TCP socket is null");
+ }
+ acceptor_->asyncAccept(*tcp_socket_, cb);
+ } else {
+ if (!tls_socket_) {
+ isc_throw(Unexpected, "internal error: TLS socket is null");
+ }
+ tls_acceptor->asyncAccept(*tls_socket_, cb);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(TcpConnectionError, "unable to start accepting TCP "
+ "connections: " << ex.what());
+ }
+}
+
+void
+TcpConnection::doHandshake() {
+ // Skip the handshake if the socket is not a TLS one.
+ if (!tls_socket_) {
+ doRead();
+ return;
+ }
+
+ // 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(&TcpConnection::handshakeCallback,
+ shared_from_this(),
+ ph::_1)); // error
+ try {
+ tls_socket_->handshake(cb);
+
+ } catch (const std::exception& ex) {
+ isc_throw(TcpConnectionError, "unable to perform TLS handshake: "
+ << ex.what());
+ }
+}
+
+void
+TcpConnection::doRead(TransactionPtr transaction) {
+ try {
+ TCPEndpoint endpoint;
+
+ // Transaction hasn't been created if we are starting to read the
+ // new request.
+ if (!transaction) {
+ transaction = Transaction::create(response_creator_);
+ recordParameters(transaction->getRequest());
+ }
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&TcpConnection::socketReadCallback,
+ shared_from_this(),
+ transaction,
+ ph::_1, // error
+ ph::_2)); //bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+ transaction->getInputBufSize(),
+ 0, &endpoint, cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+ transaction->getInputBufSize(),
+ 0, &endpoint, cb);
+ return;
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+TcpConnection::doWrite(TcpConnection::TransactionPtr transaction) {
+ try {
+ if (transaction->outputDataAvail()) {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&TcpConnection::socketWriteCallback,
+ shared_from_this(),
+ transaction,
+ ph::_1, // error
+ ph::_2)); // bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncSend(transaction->getOutputBufData(),
+ transaction->getOutputBufSize(),
+ cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncSend(transaction->getOutputBufData(),
+ transaction->getOutputBufSize(),
+ cb);
+ return;
+ }
+ } else {
+ // The isPersistent() function may throw if the request hasn't
+ // been created, i.e. the HTTP headers weren't parsed. We catch
+ // this exception below and close the connection since we're
+ // unable to tell if the connection should remain persistent
+ // or not. The default is to close it.
+ if (!transaction->getRequest()->isPersistent()) {
+ stopThisConnection();
+
+ } else {
+ // The connection is persistent and we are done sending
+ // the previous response. Start listening for the next
+ // requests.
+ setupIdleTimer();
+ doRead();
+ }
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+TcpConnection::asyncSendResponse(const ConstTcpResponsePtr& response,
+ TransactionPtr transaction) {
+ transaction->setOutputBuf(response->toString());
+ doWrite(transaction);
+}
+
+
+void
+TcpConnection::acceptorCallback(const boost::system::error_code& ec) {
+ if (!acceptor_->isOpen()) {
+ return;
+ }
+
+ if (ec) {
+ stopThisConnection();
+ }
+
+ acceptor_callback_(ec);
+
+ if (!ec) {
+ if (!tls_context_) {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ } else {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_CONNECTION_HANDSHAKE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ }
+
+ setupRequestTimer();
+ doHandshake();
+ }
+}
+
+void
+TcpConnection::handshakeCallback(const boost::system::error_code& ec) {
+ if (ec) {
+ LOG_INFO(asiolink_logger, TCP_CONNECTION_HANDSHAKE_FAILED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ec.message());
+ stopThisConnection();
+ } else {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTPS_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText());
+
+ doRead();
+ }
+}
+
+void
+TcpConnection::socketReadCallback(TcpConnection::TransactionPtr transaction,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt. Just make sure
+ // we don't try to read anything now in case there is any garbage
+ // passed in length.
+ } else {
+ length = 0;
+ }
+ }
+
+ // Receiving is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+
+ if (length != 0) {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ TCP_DATA_RECEIVED)
+ .arg(length)
+ .arg(getRemoteEndpointAddressAsText());
+
+ transaction->getParser()->postBuffer(static_cast<void*>(transaction->getInputBufData()),
+ length);
+ transaction->getParser()->poll();
+ }
+
+ if (transaction->getParser()->needData()) {
+ // The parser indicates that the some part of the message being
+ // received is still missing, so continue to read.
+ doRead(transaction);
+
+ } else {
+ try {
+ // The whole message has been received, so let's finalize it.
+ transaction->getRequest()->finalize();
+
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ TCP_CLIENT_REQUEST_RECEIVED_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+
+ } catch (const std::exception& ex) {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_BAD_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ex.what());
+
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ TCP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+ }
+
+ // Don't want to timeout if creation of the response takes long.
+ request_timer_.cancel();
+
+ // Create the response from the received request using the custom
+ // response creator.
+ TcpResponsePtr response = response_creator_->createTcpResponse(transaction->getRequest());
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_SERVER_RESPONSE_SEND)
+ .arg(response->toBriefString())
+ .arg(getRemoteEndpointAddressAsText());
+
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ TCP_SERVER_RESPONSE_SEND_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(TcpMessageParserBase::logFormatTcpMessage(response->toString(),
+ MAX_LOGGED_MESSAGE_SIZE));
+
+ // Response created. Activate the timer again.
+ setupRequestTimer(transaction);
+
+ // Start sending the response.
+ asyncSendResponse(response, transaction);
+ }
+}
+
+void
+TcpConnection::socketWriteCallback(TcpConnection::TransactionPtr transaction,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt.
+ } else {
+ // Sending is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+
+ doWrite(transaction);
+ }
+ }
+
+ // Since each transaction has its own output buffer, it is not really
+ // possible that the number of bytes written is larger than the size
+ // of the buffer. But, let's be safe and set the length to the size
+ // of the buffer if that unexpected condition occurs.
+ if (length > transaction->getOutputBufSize()) {
+ length = transaction->getOutputBufSize();
+ }
+
+ if (length <= transaction->getOutputBufSize()) {
+ // Sending is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+ }
+
+ // Eat the 'length' number of bytes from the output buffer and only
+ // leave the part of the response that hasn't been sent.
+ transaction->consumeOutputBuf(length);
+
+ // Schedule the write of the unsent data.
+ doWrite(transaction);
+}
+
+void
+TcpConnection::setupRequestTimer(TransactionPtr transaction) {
+ // Pass raw pointer rather than shared_ptr to this object,
+ // because IntervalTimer already passes shared pointer to the
+ // IntervalTimerImpl to make sure that the callback remains
+ // valid.
+ request_timer_.setup(std::bind(&TcpConnection::requestTimeoutCallback,
+ this, transaction),
+ request_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+TcpConnection::setupIdleTimer() {
+ request_timer_.setup(std::bind(&TcpConnection::idleTimeoutCallback,
+ this),
+ idle_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+TcpConnection::requestTimeoutCallback(TransactionPtr transaction) {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_CLIENT_REQUEST_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ // We need to differentiate the transactions between a normal response and the
+ // timeout. We create new transaction from the current transaction. It is
+ // to preserve the request we're responding to.
+ auto spawned_transaction = Transaction::spawn(response_creator_, transaction);
+
+ // The new transaction inherits the request from the original transaction
+ // if such transaction exists.
+ auto request = spawned_transaction->getRequest();
+
+ // Depending on when the timeout occurred, the TCP version of the request
+ // may or may not be available. Therefore we check if the HTTP version is
+ // set in the request. If it is not available, we need to create a dummy
+ // request with the default HTTP/1.0 version. This version will be used
+ // in the response.
+ if (request->context()->http_version_major_ == 0) {
+ request.reset(new TcpRequest(TcpRequest::Method::HTTP_POST, "/",
+ TcpVersion::HTTP_10(),
+ HostTcpHeader("dummy")));
+ request->finalize();
+ }
+
+ // Create the timeout response.
+ TcpResponsePtr response =
+ response_creator_->createStockTcpResponse(request,
+ TcpStatusCode::REQUEST_TIMEOUT);
+
+ // Send the HTTP 408 status.
+ asyncSendResponse(response, spawned_transaction);
+}
+
+void
+TcpConnection::idleTimeoutCallback() {
+ LOG_DEBUG(asiolink_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+ // In theory we should shutdown first and stop/close after but
+ // it is better to put the connection management responsibility
+ // on the client... so simply drop idle connections.
+ stopThisConnection();
+}
+
+std::string
+TcpConnection::getRemoteEndpointAddressAsText() const {
+ try {
+ if (tcp_socket_) {
+ if (tcp_socket_->getASIOSocket().is_open()) {
+ return (tcp_socket_->getASIOSocket().remote_endpoint().address().to_string());
+ }
+ } else if (tls_socket_) {
+ if (tls_socket_->getASIOSocket().is_open()) {
+ return (tls_socket_->getASIOSocket().remote_endpoint().address().to_string());
+ }
+ }
+ } catch (...) {
+ }
+ return ("(unknown address)");
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
+#endif
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_CONNECTION_H
+#define TCP_CONNECTION_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <tcp/tcp_connection_acceptor.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <array>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace tcp {
+
+/// @todo TKM these are place holders while I think output how it should work
+typedef util::InputBuffer TcpRequest;
+typedef boost::shared_ptr<TcpRequest> TcpRequestPtr;
+
+typedef util::OutputBuffer TcpResponse;
+typedef boost::shared_ptr<TcpResponse> TcpResponsePtr;
+
+
+/// @brief Generic error reported within @ref TcpConnection class.
+class TcpConnectionError : public Exception {
+public:
+ TcpConnectionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the @ref TcpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref TcpConnectionPool to avoid circular inclusion.
+class TcpConnectionPool;
+
+class TcpConnection;
+/// @brief Pointer to the @ref TcpConnection.
+typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
+
+/// @brief Accepts and handles a single TCP connection.
+class TcpConnection : public boost::enable_shared_from_this<TcpConnection> {
+private:
+
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
+ typedef std::function<void(boost::system::error_code ec, size_t length)>
+ SocketCallbackFunction;
+
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
+ class SocketCallback {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
+ void operator()(boost::system::error_code ec, size_t length = 0);
+
+ private:
+ /// @brief Supplied callback.
+ SocketCallbackFunction callback_;
+ };
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new TCP connections.
+ /// @param tls_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 TCP response from the TCP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a TCP request.
+ /// @param idle_timeout Timeout after which persistent TCP connection is
+ /// closed by the server.
+ TcpConnection(asiolink::IOService& io_service,
+ const TcpConnectionAcceptorPtr& acceptor,
+ const asiolink::TlsContextPtr& tls_context,
+ TcpConnectionPool& connection_pool,
+ const TcpConnectionAcceptorCallback& callback,
+ const long idle_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Closes current connection.
+ virtual ~TcpConnection();
+
+ /// @brief Asynchronously accepts new connection.
+ ///
+ /// When the connection is established successfully, the timeout timer is
+ /// setup and the asynchronous handshake with client is performed.
+ void asyncAccept();
+
+ /// @brief Shutdown the socket.
+ void shutdown();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Asynchronously performs TLS handshake.
+ ///
+ /// When the handshake is performed successfully or skipped because TLS
+ /// was not enabled, 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 TCP parser until
+ /// the parser signals that the entire request has been received or until
+ /// the parser signals an error. In the former case the server creates an
+ /// TCP response using supplied response creator object.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param request Pointer to the request for which the read
+ /// operation should be performed. It defaults to null pointer which
+ /// indicates that this function should create new request.
+ void doRead();
+
+protected:
+
+ /// @brief Starts asynchronous write to the socket.
+ ///
+ /// The @c output_buf_ must contain the data to be sent.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param request Pointer to the request for which the write
+ /// operation should be performed.
+ void doWrite();
+
+ /// @brief Sends TCP response asynchronously.
+ ///
+ /// Internally it calls @ref TcpConnection::doWrite to send the data.
+ ///
+ /// @param response Pointer to the TCP response to be sent.
+ /// @param request Pointer to the request.
+ void asyncSendResponse(const TcpResponsePtr& response);
+
+ /// @brief Local callback invoked when new connection is accepted.
+ ///
+ /// 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 TcpConnection::doRead or
+ /// @ref TcpConnection::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
+ /// TcpConnection::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 TCP parser and continues
+ /// parsing. When the parser signals end of the TCP request the callback
+ /// prepares a response and starts asynchronous send over the socket.
+ ///
+ /// @param request Pointer to the request for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the received data.
+ void socketReadCallback(TcpRequestPtr request,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param request Pointer to the request for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(TcpRequestPtr request,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when TLS shutdown is performed.
+ ///
+ /// The TLS socket is unconditionally closed but the callback is called
+ /// only when the peer has answered so the connection should be
+ /// explicitly closed in all cases, i.e. do not rely on this handler.
+ ///
+ /// @param ec Error code (ignored).
+ void shutdownCallback(const boost::system::error_code& ec);
+
+ /// @brief Reset timer for detecting request timeouts.
+ ///
+ /// @param request Pointer to the request to be guarded by the timeout.
+ void setupRequestTimer(TcpRequestPtr request = TcpRequestPtr());
+
+ /// @brief Reset timer for detecting idle timeout in persistent connections.
+ void setupIdleTimer();
+
+ /// @brief Callback invoked when the TCP Request Timeout occurs.
+ ///
+ /// This callback creates TCP response with Request Timeout error code
+ /// and sends it to the client.
+ ///
+ /// @param request Pointer to the request for which timeout occurs.
+ void requestTimeoutCallback(TcpRequestPtr request);
+
+ void idleTimeoutCallback();
+
+ /// @brief Shuts down current connection.
+ ///
+ /// Copied from the next method @ref stopThisConnection
+ void shutdownConnection();
+
+ /// @brief Stops current connection.
+ void stopThisConnection();
+
+ /// @brief returns remote address in textual form
+ std::string getRemoteEndpointAddressAsText() const;
+
+ /// @brief Timer used to detect Request Timeout.
+ asiolink::IntervalTimer request_timer_;
+
+ /// @brief Configured Request Timeout in milliseconds.
+ long request_timeout_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Timeout after which the persistent TCP connection is shut
+ /// down by the server.
+ long idle_timeout_;
+
+ /// @brief TCP socket used by this connection.
+ std::unique_ptr<asiolink::TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket used by this connection.
+ std::unique_ptr<asiolink::TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Pointer to the TCP acceptor used to accept new connections.
+ TcpConnectionAcceptorPtr acceptor_;
+
+ /// @brief Connection pool holding this connection.
+ TcpConnectionPool& connection_pool_;
+
+ /// @brief External TCP acceptor callback.
+ TcpConnectionAcceptorCallback acceptor_callback_;
+};
+
+} // end of namespace isc::tcp
+} // end of namespace isc
+
+#endif
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_ACCEPTOR_H
+#define HTTP_ACCEPTOR_H
+
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tls_acceptor.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/system/system_error.hpp>
+#include <functional>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Type of the callback for the TCP acceptor used in this library.
+typedef std::function<void(const boost::system::error_code&)> TcpConnectionAcceptorCallback;
+
+/// @brief Type of the TCP acceptor used in this library.
+typedef asiolink::TCPAcceptor<TcpConnectionAcceptorCallback> TcpConnectionAcceptor;
+
+/// @brief Type of shared pointer to TCP acceptors.
+typedef boost::shared_ptr<TcpConnectionAcceptor> TcpConnectionAcceptorPtr;
+
+/// @brief Type of the TLS acceptor used in this library.
+typedef asiolink::TLSAcceptor<TcpConnectionAcceptorCallback> TlsConnectionAcceptor;
+
+/// @brief Type of shared pointer to TLS acceptors.
+typedef boost::shared_ptr<TlsConnectionAcceptor> TlsConnectionAcceptorPtr;
+
+} // end of namespace isc::tcp
+} // end of namespace isc
+
+#endif
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#if 0
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <tcp/tcp_connection_pool.h>
+#include <util/multi_threading_mgr.h>
+
+namespace isc {
+namespace tcp {
+
+void
+TcpConnectionPool::start(const TcpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.insert(connections_.end(), connection);
+ } else {
+ connections_.insert(connections_.end(), connection);
+ }
+
+ connection->asyncAccept();
+}
+
+void
+TcpConnectionPool::stop(const TcpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.remove(connection);
+ } else {
+ connections_.remove(connection);
+ }
+
+ connection->close();
+}
+
+void
+TcpConnectionPool::shutdown(const TcpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.remove(connection);
+ } else {
+ connections_.remove(connection);
+ }
+
+ connection->shutdown();
+}
+
+void
+TcpConnectionPool::stopAll() {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ stopAllInternal();
+ } else {
+ stopAllInternal();
+ }
+}
+
+void
+TcpConnectionPool::stopAllInternal() {
+ for (auto connection = connections_.begin();
+ connection != connections_.end();
+ ++connection) {
+ (*connection)->close();
+ }
+
+ connections_.clear();
+}
+
+}
+}
+
+#endif
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_CONNECTION_POOL_H
+#define TCP_CONNECTION_POOL_H
+
+#include <tcp/tcp_connection.h>
+
+#include <list>
+#include <mutex>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Pool of active TCP connections.
+///
+/// The TCP server is designed to handle many connections simultaneously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
+class TcpConnectionPool {
+public:
+
+ /// @brief Start new connection.
+ ///
+ /// The connection is inserted to the pool and the
+ /// @ref TcpConnection::asyncAccept is invoked.
+ ///
+ /// @param connection Pointer to the new connection.
+ void start(const TcpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and shutdown it.
+ ///
+ /// Shutdown is specific to TLS and is a first part of graceful close (note it is
+ /// NOT the same as TCP shutdown system call).
+ ///
+ /// @note if the TLS connection stalls e.g. the peer does not try I/O
+ /// on it the connection has to be explicitly stopped.
+ ///
+ /// @param connection Pointer to the connection.
+ void shutdown(const TcpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and stops it.
+ ///
+ /// @param connection Pointer to the connection.
+ void stop(const TcpConnectionPtr& connection);
+
+ /// @brief Stops all connections and removes them from the pool.
+ void stopAll();
+
+protected:
+
+ /// @brief Stops all connections and removes them from the pool.
+ ///
+ /// Must be called from with a thread-safe context.
+ void stopAllInternal();
+
+ /// @brief Set of connections.
+ std::list<TcpConnectionPtr> connections_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+};
+
+}
+}
+
+#endif
+
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <tcp/tcp_listener.h>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace tcp {
+
+TcpListener::TcpListener(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const long idle_timeout)
+ : io_service_(io_service), tls_context_(tls_context), acceptor_(),
+ endpoint_(), connections_(), idle_timeout_(idle_timeout) {
+
+ // Create the TCP or TLS acceptor.
+ // @todo TKM - hmmm.... need to understand this better..
+ if (!tls_context) {
+ acceptor_.reset(new TcpConnectionAcceptor(io_service));
+ } else {
+ acceptor_.reset(new TlsConnectionAcceptor(io_service));
+ }
+
+ // Try creating an endpoint. This may cause exceptions.
+ try {
+ endpoint_.reset(new TCPEndpoint(server_address, server_port));
+
+ } catch (...) {
+ isc_throw(TcpListenerError, "unable to create TCP endpoint for "
+ << server_address << ":" << server_port);
+ }
+
+ // Idle persistent connection timeout is signed and must be greater than 0.
+ if (idle_timeout_ <= 0) {
+ isc_throw(TcpListenerError, "Invalid desired TCP idle persistent connection"
+ " timeout " << idle_timeout_);
+ }
+}
+
+const TCPEndpoint&
+TcpListener::getEndpoint() const {
+ return (*endpoint_);
+}
+
+void
+TcpListener::start() {
+ try {
+ acceptor_->open(*endpoint_);
+ acceptor_->setOption(TcpConnectionAcceptor::ReuseAddress(true));
+ acceptor_->bind(*endpoint_);
+ acceptor_->listen();
+
+ } catch (const boost::system::system_error& ex) {
+ stop();
+ isc_throw(TcpListenerError, "unable to setup TCP acceptor for "
+ "listening for incoming TCP clients: " << ex.what());
+ }
+
+ accept();
+}
+
+void
+TcpListener::stop() {
+ connections_.stopAll();
+ acceptor_->close();
+}
+
+void
+TcpListener::accept() {
+ TcpConnectionAcceptorCallback acceptor_callback =
+ std::bind(&TcpListener::acceptHandler, this, ph::_1);
+
+ TcpConnectionPtr conn = createConnection(acceptor_callback);
+
+ // Add this new connection to the pool.
+ connections_.start(conn);
+}
+
+void
+TcpListener::acceptHandler(const boost::system::error_code&) {
+ // The new connection has arrived. Set the acceptor to continue
+ // accepting new connections.
+ accept();
+}
+
+TcpConnectionPtr
+TcpListener::createConnection(const TcpConnectionAcceptorCallback& /* callback */) {
+#if 1
+ isc_throw(NotImplemented, "TcpListener::createConnection:");
+#else
+ TcpConnectionPtr
+/// @todo TKM - I think what we want is to define TcpConnectionFactory
+/// instead of a response creator. Let TcpListener accept a factory
+/// for that, which is used here to create for BLQ an LeaseQueryConnection
+ return (connection_factory_(io_service_, acceptor_, tls_context_,
+ connections_, callback, idle_timeout_));
+#endif
+}
+
+} // end of namespace isc::tcp
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_LISTENER_H
+#define TCP_LISTENER_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+#include <tcp/tcp_connection_pool.h>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace tcp {
+
+/// @brief A generic error raised by the @ref TcpListener class.
+class TcpListenerError : public Exception {
+public:
+ TcpListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief ementation of the @ref TcpListener.
+class TcpListener {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref TcpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the TCP service should run.
+ /// @param server_port Port number on which the TCP service should run.
+ /// @param tls_context TLS context.
+ /// @param idle_timeout Timeout after which an idle persistent TCP
+ /// connection is closed by the server.
+ ///
+ /// @throw TcpListenerError when any of the specified parameters is
+ /// invalid.
+ TcpListener(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const long idle_timeout);
+
+ /// @brief Virtual destructor.
+ virtual ~TcpListener() {
+ }
+
+ /// @brief Returns reference to the current listener endpoint.
+ const asiolink::TCPEndpoint& getEndpoint() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new TCP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref TcpListener::stop is called.
+ ///
+ /// @throw TcpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+protected:
+
+ /// @brief Creates @ref TcpConnection instance and adds it to the
+ /// pool of active connections.
+ ///
+ /// The next accepted connection will be handled by this instance.
+ void accept();
+
+ /// @brief Callback invoked when the new connection is accepted.
+ ///
+ /// It calls @c TcpListener::accept to create new @c TcpConnection
+ /// instance.
+ ///
+ /// @param ec Error code passed to the handler. This is currently ignored.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Creates an instance of the @c TcpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @return Pointer to the created connection.
+ virtual TcpConnectionPtr createConnection(const TcpConnectionAcceptorCallback& callback);
+
+ /// @brief Reference to the IO service.
+ asiolink::IOService& io_service_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Acceptor instance.
+ TcpConnectionAcceptorPtr acceptor_;
+
+ /// @brief Pointer to the endpoint representing IP address and port on
+ /// which the service is running.
+ boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+ /// @brief Pool of active connections.
+ TcpConnectionPool connections_;
+
+ /// @brief Timeout after which idle persistent connection is closed by
+ /// the server.
+ long idle_timeout_;
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TCP_LISTENER_H
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at tcp://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the libkea-tcp library.
+
+#include <config.h>
+
+#include <tcp/tcp_log.h>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Defines the logger used within libkea-tcp library.
+isc::log::Logger tcp_logger("tcp");
+
+} // namespace tcp
+} // namespace isc
+
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at tcp://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_LOG_H
+#define TCP_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <tcp/tcp_messages.h>
+
+namespace isc {
+namespace tcp {
+
+/// Define the logger used within libkea-tcp library.
+extern isc::log::Logger tcp_logger;
+
+} // namespace tcp
+} // namespace isc
+
+#endif // TCP_LOG_H
--- /dev/null
+// File created from ../../../src/lib/tcp/tcp_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace asiolink {
+
+extern const isc::log::MessageID TCP_BAD_CLIENT_REQUEST_RECEIVED = "TCP_BAD_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED = "TCP_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED_DETAILS = "TCP_CLIENT_REQUEST_RECEIVED_DETAILS";
+extern const isc::log::MessageID TCP_CONNECTION_CLOSE_CALLBACK_FAILED = "TCP_CONNECTION_CLOSE_CALLBACK_FAILED";
+extern const isc::log::MessageID TCP_CONNECTION_HANDSHAKE_FAILED = "TCP_CONNECTION_HANDSHAKE_FAILED";
+extern const isc::log::MessageID TCP_CONNECTION_HANDSHAKE_START = "TCP_CONNECTION_HANDSHAKE_START";
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN = "TCP_CONNECTION_SHUTDOWN";
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN_FAILED = "TCP_CONNECTION_SHUTDOWN_FAILED";
+extern const isc::log::MessageID TCP_CONNECTION_STOP = "TCP_CONNECTION_STOP";
+extern const isc::log::MessageID TCP_CONNECTION_STOP_FAILED = "TCP_CONNECTION_STOP_FAILED";
+extern const isc::log::MessageID TCP_DATA_RECEIVED = "TCP_DATA_RECEIVED";
+extern const isc::log::MessageID TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED = "TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED = "TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID TCP_REQUEST_RECEIVE_START = "TCP_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND = "TCP_SERVER_RESPONSE_SEND";
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND_DETAILS = "TCP_SERVER_RESPONSE_SEND_DETAILS";
+
+} // namespace asiolink
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "TCP_BAD_CLIENT_REQUEST_RECEIVED", "bad request received from %1: %2",
+ "TCP_CLIENT_REQUEST_RECEIVED", "received TCP request from %1",
+ "TCP_CLIENT_REQUEST_RECEIVED_DETAILS", "detailed information about well-formed request received from %1:\n%2",
+ "TCP_CONNECTION_CLOSE_CALLBACK_FAILED", "Connection close callback threw an exception",
+ "TCP_CONNECTION_HANDSHAKE_FAILED", "TLS handshake with %1 failed with %2",
+ "TCP_CONNECTION_HANDSHAKE_START", "start TLS handshake with %1 with timeout %2",
+ "TCP_CONNECTION_SHUTDOWN", "shutting down TCP connection from %1",
+ "TCP_CONNECTION_SHUTDOWN_FAILED", "shutting down TCP connection failed",
+ "TCP_CONNECTION_STOP", "stopping TCP connection from %1",
+ "TCP_CONNECTION_STOP_FAILED", "stopping TCP connection failed",
+ "TCP_DATA_RECEIVED", "received %1 bytes from %2",
+ "TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED", "closing persistent connection with %1 as a result of a timeout",
+ "TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED", "premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3",
+ "TCP_REQUEST_RECEIVE_START", "start receiving request from %1 with timeout %2",
+ "TCP_SERVER_RESPONSE_SEND", "sending TCP response %1 to %2",
+ "TCP_SERVER_RESPONSE_SEND_DETAILS", "detailed information about response sent to %1:\n%2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
--- /dev/null
+// File created from ../../../src/lib/tcp/tcp_messages.mes
+
+#ifndef TCP_MESSAGES_H
+#define TCP_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace asiolink {
+
+extern const isc::log::MessageID TCP_BAD_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED_DETAILS;
+extern const isc::log::MessageID TCP_CONNECTION_CLOSE_CALLBACK_FAILED;
+extern const isc::log::MessageID TCP_CONNECTION_HANDSHAKE_FAILED;
+extern const isc::log::MessageID TCP_CONNECTION_HANDSHAKE_START;
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN;
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN_FAILED;
+extern const isc::log::MessageID TCP_CONNECTION_STOP;
+extern const isc::log::MessageID TCP_CONNECTION_STOP_FAILED;
+extern const isc::log::MessageID TCP_DATA_RECEIVED;
+extern const isc::log::MessageID TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID TCP_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND;
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND_DETAILS;
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // TCP_MESSAGES_H
--- /dev/null
+# Copyright (C) 2022 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$NAMESPACE isc::asiolink
+
+% TCP_BAD_CLIENT_REQUEST_RECEIVED bad request received from %1: %2
+This debug message is issued when an TCP client sends malformed request to
+the server. This includes TCP requests using unexpected content types,
+including malformed JSON etc. The first argument specifies an address of
+the remote endpoint which sent the request. The second argument provides
+a detailed error message.
+
+% TCP_CLIENT_REQUEST_RECEIVED received TCP request from %1
+This debug message is issued when the server finished receiving a TCP
+request from the remote endpoint. The address of the remote endpoint is
+specified as an argument.
+
+% TCP_CLIENT_REQUEST_RECEIVED_DETAILS detailed information about well-formed request received from %1:\n%2
+This debug message is issued when the TCP server receives a well-formed
+request. It includes detailed information about the received request. The
+first argument specifies an address of the remote endpoint which sent the
+request. The second argument provides the request in the textual format.
+The request is truncated by the logger if it is too large to be printed.
+
+% TCP_CONNECTION_CLOSE_CALLBACK_FAILED Connection close callback threw an exception
+This is an error message emitted when the close connection callback
+registered on the connection failed unexpectedly. This is a programmatic
+error that should be submitted as a bug.
+
+% TCP_CONNECTION_HANDSHAKE_FAILED TLS handshake with %1 failed with %2
+This information message is issued when the TLS handshake failed at the
+server side. The client address and the error message are displayed.
+
+% TCP_CONNECTION_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.
+
+% TCP_CONNECTION_SHUTDOWN shutting down TCP connection from %1
+This debug message is issued when one of the TCP connections is shut down.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% TCP_CONNECTION_SHUTDOWN_FAILED shutting down TCP connection failed
+This error message is issued when an error occurred during shutting down
+a TCP connection with a client.
+
+% TCP_CONNECTION_STOP stopping TCP connection from %1
+This debug message is issued when one of the TCP connections is stopped.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% TCP_CONNECTION_STOP_FAILED stopping TCP connection failed
+This error message is issued when an error occurred during closing a
+TCP connection with a client.
+
+% TCP_DATA_RECEIVED received %1 bytes from %2
+This debug message is issued when the server receives a chunk of data from
+the remote endpoint. This may include the whole request or only a part
+of the request. The first argument specifies the amount of received data.
+The second argument specifies an address of the remote endpoint which
+produced the data.
+
+% TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED closing persistent connection with %1 as a result of a timeout
+This debug message is issued when the persistent TCP connection is being
+closed as a result of being idle.
+
+% TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3
+This warning message is issued when unexpected timeout occurred during the
+transaction. This is proven to occur when the system clock is moved manually
+or as a result of synchronization with a time server. Any ongoing transactions
+will be interrupted. New transactions should be conducted normally.
+
+% TCP_REQUEST_RECEIVE_START start receiving request from %1 with timeout %2
+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.
+
+% TCP_SERVER_RESPONSE_SEND sending TCP response %1 to %2
+This debug message is issued when the server is starting to send a TCP
+response to a remote endpoint. The first argument holds basic information
+about the response (TCP version number and status code). The second
+argument specifies an address of the remote endpoint.
+
+% TCP_SERVER_RESPONSE_SEND_DETAILS detailed information about response sent to %1:\n%2
+This debug message is issued right before the server sends a TCP response
+to the client. It includes detailed information about the response. The
+first argument specifies an address of the remote endpoint to which the
+response is being sent. The second argument provides a response in the
+textual form. The response is truncated by the logger if it is too large
+to be printed.
--- /dev/null
+run_unittests
--- /dev/null
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+TEST_CA_DIR = $(abs_srcdir)/../testutils/ca
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda test-socket
+
+DISTCLEANFILES =
+
+noinst_SCRIPTS =
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+#run_unittests_SOURCES += tcp_listner_unittest.cc
+
+#if HAVE_OPENSSL
+#run_unittests_SOURCES += tls_unittest.cc
+#run_unittests_SOURCES += tls_acceptor_unittest.cc
+#run_unittests_SOURCES += tls_socket_unittest.cc
+#endif
+#if HAVE_BOTAN_BOOST
+#run_unittests_SOURCES += tls_unittest.cc
+#run_unittests_SOURCES += tls_acceptor_unittest.cc
+#run_unittests_SOURCES += tls_socket_unittest.cc
+#endif
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/tcp/libkea-tcp.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(GTEST_LDADD)
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter -Wno-unused-private-field
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
--- /dev/null
+// Copyright (C) 2022 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <log/logger_manager.h>
+
+int
+main(int argc, char* argv[])
+{
+ ::testing::InitGoogleTest(&argc, argv); // Initialize Google test
+ isc::log::LoggerManager::init("unittest"); // Set a root logger name
+ return (isc::util::unittests::run_all());
+}