summaryrefslogtreecommitdiffstats
path: root/src/lib/asiolink/unix_domain_socket.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/asiolink/unix_domain_socket.cc')
-rw-r--r--src/lib/asiolink/unix_domain_socket.cc371
1 files changed, 371 insertions, 0 deletions
diff --git a/src/lib/asiolink/unix_domain_socket.cc b/src/lib/asiolink/unix_domain_socket.cc
new file mode 100644
index 0000000..ca9e15c
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket.cc
@@ -0,0 +1,371 @@
+// Copyright (C) 2017-2020 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 <asiolink/unix_domain_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <functional>
+#include <iostream>
+
+using namespace boost::asio::local;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implementation of the unix domain socket.
+class UnixDomainSocketImpl : public boost::enable_shared_from_this<UnixDomainSocketImpl> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the socket class.
+ UnixDomainSocketImpl(IOService& io_service)
+ : socket_(io_service.get_io_service()) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the socket.
+ ~UnixDomainSocketImpl() {
+ close();
+ }
+
+ /// @brief Asynchronously connects to an endpoint.
+ ///
+ /// This method schedules asynchronous connect and installs the
+ /// @ref UnixDomainSocketImpl::connectHandler as a callback.
+ ///
+ /// @param endpoint Reference to an endpoint to connect to.
+ /// @param handler User supplied handler to be invoked when the connection
+ /// is established or when error is signalled.
+ void asyncConnect(const stream_protocol::endpoint& endpoint,
+ const UnixDomainSocket::ConnectHandler& handler);
+
+ /// @brief Local handler invoked as a result of asynchronous connection.
+ ///
+ /// This is a wrapper around the user supplied callback. It ignores
+ /// EINPROGRESS errors which are observed on some operating systems as
+ /// a result of trying to connect asynchronously. This error code doesn't
+ /// necessarily indicate a problem and the subsequent attempts to read
+ /// and write to the socket will succeed. Therefore, the handler simply
+ /// overrides this error code with success status. The user supplied
+ /// handler doesn't need to deal with the EINPROGRESS error codes.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param ec Error code returned as a result of connection.
+ void connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+ const boost::system::error_code& ec);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// This method schedules an asynchronous send and installs the
+ /// @ref UnixDomainSocketImpl::sendHandler as a callback.
+ ///
+ /// @param data Pointer to data to be sent.
+ /// @param length Number of bytes to be sent.
+ /// @param handler Callback to be invoked when data have been sent or an
+ /// sending error is signalled.
+ void asyncSend(const void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Asynchronously sends the data over the socket.
+ ///
+ /// This method is called by the @ref asyncSend and the @ref sendHandler
+ /// if the asynchronous send has to be repeated as a result of receiving
+ /// EAGAIN or EWOULDBLOCK.
+ ///
+ /// @param buffer Buffers holding the data to be sent.
+ /// @param handler User supplied callback to be invoked when data have
+ /// been sent or sending error is signalled.
+ void doSend(const boost::asio::const_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler);
+
+
+ /// @brief Local handler invoked as a result of asynchronous send.
+ ///
+ /// This handler is invoked as a result of asynchronous send. It is a
+ /// wrapper callback around the user supplied callback. It handles
+ /// EWOULDBLOCK and EAGAIN errors by retrying an asynchronous send.
+ /// These errors are often returned on some operating systems, even
+ /// though one would expect that asynchronous operation would not
+ /// return such errors. Because these errors are handled by the
+ /// wrapper callback, the user supplied callback never receives
+ /// these errors.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param buffer Buffers holding the data to be sent.
+ /// @param ec Error code returned as a result of sending the data.
+ /// @param length Length of the data sent.
+ void sendHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::const_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Asynchronously receive data over the socket.
+ ///
+ /// This method schedules asynchronous receive and installs the
+ /// @ref UnixDomainSocketImpl::receiveHandler is a callback.
+ ///
+ /// @param data Pointer to a buffer into which the data should be read.
+ /// @param length Length of the buffer.
+ /// @param handler User supplied callback invoked when data have been
+ /// received or an error is signalled.
+ void asyncReceive(void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Asynchronously receives the data over the socket.
+ ///
+ /// This method is called @ref asyncReceive and @ref receiveHandler when
+ /// EWOULDBLOCK or EAGAIN is returned.
+ ///
+ /// @param buffer A buffer into which the data should be received.
+ /// @param handler User supplied callback invoked when data have been
+ /// received on an error is signalled.
+ void doReceive(const boost::asio::mutable_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Local handler invoked as a result of asynchronous receive.
+ ///
+ /// This handler is invoked as a result of asynchronous receive. It is a
+ /// wrapper callback around the user supplied callback. It handles
+ /// EWOULDBLOCK and EAGAIN by retrying to asynchronously receive the
+ /// data. These errors are often returned on some operating systems, even
+ /// though one would expect that asynchronous operation would not
+ /// return such errors. Because these errors are handled by the
+ /// wrapper callback, the user supplied callback never receives
+ /// these errors.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param buffer Buffer into which the data are received.
+ /// @param ec Error code returned as a result of asynchronous receive.
+ /// @param length Size of the received data.
+ void receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::mutable_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Disables read and write operations on the socket.
+ void shutdown();
+
+ /// @brief Cancels asynchronous operations on the socket.
+ void cancel();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Instance of the boost asio unix domain socket.
+ stream_protocol::socket socket_;
+};
+
+void
+UnixDomainSocketImpl::asyncConnect(const stream_protocol::endpoint& endpoint,
+ const UnixDomainSocket::ConnectHandler& handler) {
+ auto local_handler = std::bind(&UnixDomainSocketImpl::connectHandler,
+ shared_from_this(),
+ handler, ph::_1);
+ socket_.async_connect(endpoint, local_handler);
+}
+
+void
+UnixDomainSocketImpl::connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+ const boost::system::error_code& ec) {
+ // It was observed on Debian and Fedora that asynchronous connect may result
+ // in EINPROGRESS error. This doesn't really indicate a problem with a
+ // connection. If we continue transmitting data over the socket it will
+ // succeed. So we suppress this error and return 'success' to the user's
+ // handler.
+ if (ec.value() == boost::asio::error::in_progress) {
+ remote_handler(boost::system::error_code());
+ } else {
+ remote_handler(ec);
+ }
+}
+
+void
+UnixDomainSocketImpl::asyncSend(const void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler) {
+ doSend(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doSend(const boost::asio::const_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler) {
+ auto local_handler = std::bind(&UnixDomainSocketImpl::sendHandler,
+ shared_from_this(),
+ handler, buffer, ph::_1, ph::_2);
+ socket_.async_send(buffer, local_handler);
+}
+
+void
+UnixDomainSocketImpl::sendHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::const_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length) {
+ // The asynchronous send may return EWOULDBLOCK or EAGAIN on some
+ // operating systems. In this case, we simply retry hoping that it
+ // will succeed next time. The user's callback never sees these
+ // errors.
+ if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ doSend(buffer, remote_handler);
+
+ } else {
+ remote_handler(ec, length);
+ }
+}
+
+void
+UnixDomainSocketImpl::asyncReceive(void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler) {
+ doReceive(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doReceive(const boost::asio::mutable_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler) {
+ auto local_handler = std::bind(&UnixDomainSocketImpl::receiveHandler,
+ shared_from_this(),
+ handler, buffer, ph::_1, ph::_2);
+ socket_.async_receive(buffer, 0, local_handler);
+}
+
+void
+UnixDomainSocketImpl::receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::mutable_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length) {
+ // The asynchronous receive may return EWOULDBLOCK or EAGAIN on some
+ // operating systems. In this case, we simply retry hoping that it
+ // will succeed next time. The user's callback never sees these
+ // errors.
+ if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ doReceive(buffer, remote_handler);
+
+ } else {
+ remote_handler(ec, length);
+ }
+}
+
+void
+UnixDomainSocketImpl::shutdown() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.shutdown(stream_protocol::socket::shutdown_both, ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocketImpl::cancel() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.cancel(ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocketImpl::close() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.close(ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+UnixDomainSocket::UnixDomainSocket(IOService& io_service)
+ : impl_(new UnixDomainSocketImpl(io_service)) {
+}
+
+int
+UnixDomainSocket::getNative() const {
+#if BOOST_VERSION < 106600
+ return (impl_->socket_.native());
+#else
+ return (impl_->socket_.native_handle());
+#endif
+}
+
+int
+UnixDomainSocket::getProtocol() const {
+ return (0);
+}
+
+void
+UnixDomainSocket::connect(const std::string& path) {
+ boost::system::error_code ec;
+ impl_->socket_.connect(stream_protocol::endpoint(path.c_str()), ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocket::asyncConnect(const std::string& path, const ConnectHandler& handler) {
+ impl_->asyncConnect(stream_protocol::endpoint(path.c_str()), handler);
+}
+
+size_t
+UnixDomainSocket::write(const void* data, size_t length) {
+ boost::system::error_code ec;
+ size_t res = boost::asio::write(impl_->socket_,
+ boost::asio::buffer(data, length),
+ boost::asio::transfer_all(),
+ ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+ return (res);
+}
+
+void
+UnixDomainSocket::asyncSend(const void* data, const size_t length,
+ const Handler& handler) {
+ impl_->asyncSend(data, length, handler);
+}
+
+size_t
+UnixDomainSocket::receive(void* data, size_t length) {
+ boost::system::error_code ec;
+ size_t res = impl_->socket_.receive(boost::asio::buffer(data, length), 0, ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+ return (res);
+}
+
+void
+UnixDomainSocket::asyncReceive(void* data, const size_t length,
+ const Handler& handler) {
+ impl_->asyncReceive(data, length, handler);
+}
+
+void
+UnixDomainSocket::shutdown() {
+ impl_->shutdown();
+}
+
+void
+UnixDomainSocket::cancel() {
+ impl_->cancel();
+}
+
+void
+UnixDomainSocket::close() {
+ impl_->close();
+}
+
+boost::asio::local::stream_protocol::socket&
+UnixDomainSocket::getASIOSocket() const {
+ return (impl_->socket_);
+}
+
+} // end of namespace asiolink
+} // end of namespace isc