From: Marcin Siodelski Date: Fri, 10 Mar 2017 13:37:56 +0000 (+0100) Subject: [5078] Created UnixDomainSocket class in asiolink. X-Git-Tag: trac5196_base~14^2~14 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0576c80854342b2a13abfb0b28c2bfdd1a088227;p=thirdparty%2Fkea.git [5078] Created UnixDomainSocket class in asiolink. --- diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index 8f47c91e4d..edba478ed7 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -32,6 +32,7 @@ libkea_asiolink_la_SOURCES += tcp_endpoint.h libkea_asiolink_la_SOURCES += tcp_socket.h libkea_asiolink_la_SOURCES += udp_endpoint.h libkea_asiolink_la_SOURCES += udp_socket.h +libkea_asiolink_la_SOURCES += unix_domain_socket.cc unix_domain_socket.h # Note: the ordering matters: -Wno-... must follow -Wextra (defined in # KEA_CXXFLAGS) diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index f2d1a361cf..0b1611bc9a 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -1,6 +1,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib AM_CPPFLAGS += $(BOOST_INCLUDES) AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\" +AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/asiolink/tests\" AM_CXXFLAGS = $(KEA_CXXFLAGS) @@ -8,7 +9,7 @@ if USE_STATIC_LINK AM_LDFLAGS = -static endif -CLEANFILES = *.gcno *.gcda +CLEANFILES = *.gcno *.gcda test-socket TESTS_ENVIRONMENT = \ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) @@ -28,6 +29,7 @@ run_unittests_SOURCES += udp_socket_unittest.cc run_unittests_SOURCES += io_service_unittest.cc run_unittests_SOURCES += dummy_io_callback_unittest.cc run_unittests_SOURCES += tcp_acceptor_unittest.cc +run_unittests_SOURCES += unix_domain_socket_unittest.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) diff --git a/src/lib/asiolink/tests/unix_domain_socket_unittest.cc b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc new file mode 100644 index 0000000000..4e125dfb73 --- /dev/null +++ b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc @@ -0,0 +1,193 @@ +// Copyright (C) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost::asio; +using namespace boost::asio::local; +using namespace isc::asiolink; + +namespace { + +/// @brief Test unix socket file name. +const std::string TEST_SOCKET = "test-socket"; + +/// @brief Test timeout in ms. +const long TEST_TIMEOUT = 10000; + +/// @brief Test fixture class for @ref UnixDomainSocket class. +class UnixDomainSocketTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Removes unix socket descriptor before the test. + UnixDomainSocketTest() : io_service_(), + server_endpoint_(unixSocketFilePath()), + server_acceptor_(io_service_.get_io_service()), + server_socket_(io_service_.get_io_service()), + test_timer_(io_service_) { + removeUnixSocketFile(); + test_timer_.setup(boost::bind(&UnixDomainSocketTest::timeoutHandler, this), + TEST_TIMEOUT, IntervalTimer::ONE_SHOT); + } + + /// @brief Destructor. + /// + /// Removes unix socket descriptor after the test. + virtual ~UnixDomainSocketTest() { + removeUnixSocketFile(); + } + + /// @brief Returns socket file path. + static std::string unixSocketFilePath() { + std::ostringstream s; + s << TEST_DATA_BUILDDIR << "/" << TEST_SOCKET; + return (s.str()); + } + + /// @brief Removes unix socket descriptor. + void removeUnixSocketFile() { + static_cast(remove(unixSocketFilePath().c_str())); + } + + /// @brief Creates and binds server socket. + void bindServerSocket() { + server_acceptor_.open(); + server_acceptor_.bind(server_endpoint_); + server_acceptor_.listen(); + server_acceptor_.async_accept(server_socket_, + boost::bind(&UnixDomainSocketTest:: + acceptHandler, this, _1)); + } + + /// @brief Server acceptor handler. + /// + /// @param ec Error code. + void acceptHandler(const boost::system::error_code& ec) { + if (ec) { + ADD_FAILURE() << ec.message(); + } + server_socket_.async_read_some(boost::asio::buffer(&raw_buf_[0], + raw_buf_.size()), + boost::bind(&UnixDomainSocketTest:: + readHandler, this, _1, _2)); + } + + /// @brief Server read handler. + /// + /// @param ec Error code. + /// @param bytes_transferred Number of bytes read. + void readHandler(const boost::system::error_code& ec, + size_t bytes_transferred) { + std::string received(&raw_buf_[0], bytes_transferred); + std::string response("received " + received); + boost::asio::write(server_socket_, boost::asio::buffer(response.c_str(), + response.size())); + io_service_.stop(); + } + + /// @brief Callback function invoke upon test timeout. + /// + /// It stops the IO service and reports test timeout. + void timeoutHandler() { + ADD_FAILURE() << "Timeout occurred while running the test!"; + io_service_.stop(); + } + + /// @brief IO service used by the tests. + IOService io_service_; + + /// @brief Server endpoint. + local::stream_protocol::endpoint server_endpoint_; + + /// @brief Server acceptor. + local::stream_protocol::acceptor server_acceptor_; + + /// @brief Server side unix domain socket. + stream_protocol::socket server_socket_; + + /// @brief Receive buffer. + std::array raw_buf_; + + /// @brief Asynchronous timer service to detect timeouts. + IntervalTimer test_timer_; +}; + +// This test verifies that the client can send data over the unix +// domain socket and receive a response. +TEST_F(UnixDomainSocketTest, sendReceive) { + // Start the server. + bindServerSocket(); + + // Setup client side. + UnixDomainSocket socket(io_service_); + ASSERT_NO_THROW(socket.connect(TEST_SOCKET)); + + // Send "foo". + const std::string outbound_data = "foo"; + size_t sent_size = 0; + ASSERT_NO_THROW(sent_size = socket.write(outbound_data.c_str(), + outbound_data.size())); + // Make sure all data have been sent. + ASSERT_EQ(outbound_data.size(), sent_size); + + // Run IO service to generate server's response. + io_service_.run(); + + // Receive response from the socket. + std::array read_buf; + size_t bytes_read = 0; + ASSERT_NO_THROW(bytes_read = socket.receive(&read_buf[0], read_buf.size())); + std::string response(&read_buf[0], bytes_read); + + // The server should prepend "received" to the data we had sent. + EXPECT_EQ("received foo", response); +} + +// This test verifies that UnixDomainSocketError exception is thrown +// on attempt to connect, write or receive when the server socket +// is not available. +TEST_F(UnixDomainSocketTest, clientErrors) { + UnixDomainSocket socket(io_service_); + ASSERT_THROW(socket.connect(TEST_SOCKET), UnixDomainSocketError); + const std::string outbound_data = "foo"; + ASSERT_THROW(socket.write(outbound_data.c_str(), outbound_data.size()), + UnixDomainSocketError); + std::array read_buf; + ASSERT_THROW(socket.receive(&read_buf[0], read_buf.size()), + UnixDomainSocketError); +} + +// Check that native socket descriptor is returned correctly when +// the socket is connected. +TEST_F(UnixDomainSocketTest, getNative) { + // Start the server. + bindServerSocket(); + + // Setup client side. + UnixDomainSocket socket(io_service_); + ASSERT_NO_THROW(socket.connect(TEST_SOCKET)); + ASSERT_GE(socket.getNative(), 0); +} + +// Check that protocol returned is 0. +TEST_F(UnixDomainSocketTest, getProtocol) { + UnixDomainSocket socket(io_service_); + EXPECT_EQ(0, socket.getProtocol()); +} + +} diff --git a/src/lib/asiolink/unix_domain_socket.cc b/src/lib/asiolink/unix_domain_socket.cc new file mode 100644 index 0000000000..7b00a18529 --- /dev/null +++ b/src/lib/asiolink/unix_domain_socket.cc @@ -0,0 +1,96 @@ +// Copyright (C) 2017 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 +#include +#include +using namespace boost::asio::local; + +namespace isc { +namespace asiolink { + +/// @brief Implementation of the unix domain socket. +class 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 Closes the socket. + void close(); + + /// @brief Instance of the boost asio unix domain socket. + stream_protocol::socket socket_; +}; + +void +UnixDomainSocketImpl::close() { + static_cast(socket_.close()); +} + +UnixDomainSocket::UnixDomainSocket(IOService& io_service) + : impl_(new UnixDomainSocketImpl(io_service)) { +} + +int +UnixDomainSocket::getNative() const { + return (impl_->socket_.native()); +} + +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()); + } +} + +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), + ec); + if (ec) { + isc_throw(UnixDomainSocketError, ec.message()); + } + return (res); +} + +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::close() { + impl_->close(); +} + +} +} diff --git a/src/lib/asiolink/unix_domain_socket.h b/src/lib/asiolink/unix_domain_socket.h new file mode 100644 index 0000000000..07951ad2e2 --- /dev/null +++ b/src/lib/asiolink/unix_domain_socket.h @@ -0,0 +1,89 @@ +// Copyright (C) 2017 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 UNIX_DOMAIN_SOCKET_H +#define UNIX_DOMAIN_SOCKET_H + +#include +#include +#include +#include + +namespace isc { +namespace asiolink { + +/// @brief Exception thrown upon socket error. +class UnixDomainSocketError : public Exception { +public: + UnixDomainSocketError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +class UnixDomainSocketImpl; + +/// @brief Represents unix domain socket implemented in terms +/// of boost asio. +class UnixDomainSocket : public IOSocket { +public: + + /// @brief Constructor. + /// + /// @param io_service Reference to IOService to be used by this + /// class. + UnixDomainSocket(IOService& io_service); + + /// @brief Returns native socket representation. + virtual int getNative() const; + + /// @brief Always returns 0. + virtual int getProtocol() const; + + /// @brief Connects the socket to the specified endpoint. + /// + /// @param endpoint Endpoint to connect to. + /// @param [out] ec Error code returned as a result of an attempt to + /// connect. + /// + /// @throw UnixDomainSocketError if error occurs. + void connect(const std::string& path); + + /// @brief Writes specified amount of data to a socket. + /// + /// @param data Pointer to data to be written. + /// @param length Number of bytes to be written. + /// @param [out] ec Error code returned as a result of an attempt to + /// write to socket. + /// + /// @return Number of bytes written. + /// @throw UnixDomainSocketError if error occurs. + size_t write(const void* data, size_t length); + + /// @brief Receives data from a socket. + /// + /// @param [out] data Pointer to a location into which the read data should + /// be stored. + /// @param length Length of the buffer. + /// @param [out] ec Error code returned as a result of an attempt to + /// read from socket. + /// + /// @return Number of bytes read. + /// @throw UnixDomainSocketError if error occurs. + size_t receive(void* data, size_t length); + + /// @brief Closes the socket. + void close(); + +private: + + /// @brief Pointer to the implementation of this class. + boost::shared_ptr impl_; + +}; + +} // end of namespace isc::asiolink +} // end of namespace isc + +#endif // UNIX_DOMAIN_SOCKET_H