Commit 920ba906 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac5094'

parents 715d18f9 734aee45
......@@ -7,6 +7,7 @@
#ifndef CTRL_DHCPV4_SRV_H
#define CTRL_DHCPV4_SRV_H
#include <asiolink/asio_wrapper.h>
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/command_interpreter.h>
......
......@@ -7,6 +7,7 @@
#ifndef CTRL_DHCPV6_SRV_H
#define CTRL_DHCPV6_SRV_H
#include <asiolink/asio_wrapper.h>
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/command_interpreter.h>
......
......@@ -6,6 +6,7 @@
#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/asiolink.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/parsers/dhcp_config_parser.h>
......
......@@ -27,6 +27,7 @@ libkea_asiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
libkea_asiolink_la_SOURCES += io_error.h
libkea_asiolink_la_SOURCES += io_service.h io_service.cc
libkea_asiolink_la_SOURCES += io_socket.h io_socket.cc
libkea_asiolink_la_SOURCES += tcp_acceptor.h
libkea_asiolink_la_SOURCES += tcp_endpoint.h
libkea_asiolink_la_SOURCES += tcp_socket.h
libkea_asiolink_la_SOURCES += udp_endpoint.h
......
......@@ -78,6 +78,7 @@ class IOEndpoint;
template <typename C>
class IOAsioSocket : public IOSocket {
///
/// \name Constructors and Destructor
///
......
// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2010-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
......@@ -35,6 +35,16 @@ namespace asiolink {
/// derived class for testing purposes rather than providing factory methods
/// (i.e., getDummy variants below).
class IOSocket {
public:
/// @name Types of objects encapsulating socket options.
//@{
/// @brief Represents SO_REUSEADDR socket option.
typedef boost::asio::socket_base::reuse_address ReuseAddress;
//@}
///
/// \name Constructors and Destructor
///
......
// Copyright (C) 2016-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 TCP_ACCEPTOR_H
#define TCP_ACCEPTOR_H
#ifndef BOOST_ASIO_HPP
#error "asio.hpp must be included before including this, see asiolink.h as to why"
#endif
#include <asiolink/io_service.h>
#include <asiolink/io_socket.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/tcp_socket.h>
#include <boost/shared_ptr.hpp>
#include <netinet/in.h>
namespace isc {
namespace asiolink {
/// @brief Provides a service for accepting new TCP connections.
///
/// Internally it uses @c boost::asio::ip::tcp::acceptor class to implement
/// the acceptor service.
///
/// @tparam C Acceptor callback type.
template<typename C>
class TCPAcceptor : public IOSocket {
public:
/// @brief Constructor.
///
/// @param io_service IO service.
explicit TCPAcceptor(IOService& io_service)
: IOSocket(),
acceptor_(new boost::asio::ip::tcp::acceptor(io_service.get_io_service())) {
}
/// @brief Destructor.
virtual ~TCPAcceptor() { }
/// @brief Returns file descriptor of the underlying socket.
virtual int getNative() const final {
return (acceptor_->native());
}
/// @brief Returns protocol of the socket.
///
/// @return IPPROTO_TCP.
virtual int getProtocol() const final {
return (IPPROTO_TCP);
}
/// @brief Opens acceptor socket given the endpoint.
///
/// @param endpoint Reference to the endpoint object which specifies the
/// address and port on which the acceptor service will run.
void open(const TCPEndpoint& endpoint) {
acceptor_->open(endpoint.getASIOEndpoint().protocol());
}
/// @brief Sets socket option.
///
/// Typically, this method is used to set SO_REUSEADDR option on the socket:
/// @code
/// IOService io_service;
/// TCPAcceptor<Callback> acceptor(io_service);
/// acceptor.setOption(TCPAcceptor::ReuseAddress(true))
/// @endcode
///
/// @param socket_option Reference to the object encapsulating an option to
/// be set for the socket.
/// @tparam SettableSocketOption Type of the object encapsulating socket option
/// being set.
template<typename SettableSocketOption>
void setOption(const SettableSocketOption& socket_option) {
acceptor_->set_option(socket_option);
}
/// @brief Binds socket to an endpoint.
///
/// @param endpoint Reference to an endpoint to which the socket is to
/// be bound.
void bind(const TCPEndpoint& endpoint) {
acceptor_->bind(endpoint.getASIOEndpoint());
}
/// @brief Starts listening for the new connections.
void listen() {
acceptor_->listen();
}
/// @brief Asynchronously accept new connection.
///
/// This method accepts new connection into the specified socket. When the
/// new connection arrives or an error occurs the specified callback function
/// is invoked.
///
/// @param socket Socket into which connection should be accepted.
/// @param callback Callback function to be invoked when the new connection
/// arrives.
/// @tparam SocketCallback Type of the callback for the @ref TCPSocket.
template<typename SocketCallback>
void asyncAccept(const TCPSocket<SocketCallback>& socket, C& callback) {
acceptor_->async_accept(socket.getASIOSocket(), callback);
}
/// @brief Checks if the acceptor is open.
///
/// @return true if acceptor is open.
bool isOpen() const {
return (acceptor_->is_open());
}
/// @brief Closes the acceptor.
void close() const {
acceptor_->close();
}
private:
/// @brief Underlying ASIO acceptor implementation.
boost::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor_;
};
} // namespace asiolink
} // namespace isc
#endif
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2016 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
......@@ -153,6 +153,12 @@ public:
/// \brief Close socket
virtual void close();
/// \brief Returns reference to the underlying ASIO socket.
///
/// \return Reference to underlying ASIO socket.
virtual boost::asio::ip::tcp::socket& getASIOSocket() const {
return (socket_);
}
private:
// Two variables to hold the socket - a socket and a pointer to it. This
......
......@@ -27,6 +27,7 @@ run_unittests_SOURCES += udp_endpoint_unittest.cc
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_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
......
// Copyright (C) 2016-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 <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/interval_timer.h>
#include <asiolink/io_address.h>
#include <asiolink/io_service.h>
#include <asiolink/tcp_acceptor.h>
#include <asiolink/tcp_endpoint.h>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
#include <list>
#include <netinet/in.h>
#include <string>
using namespace isc::asiolink;
namespace {
/// @brief Local server address used for testing.
const char SERVER_ADDRESS[] = "127.0.0.1";
/// @brief Local server port used for testing.
const unsigned short SERVER_PORT = 18123;
/// @brief Test timeout in ms.
const long TEST_TIMEOUT = 10000;
/// @brief Simple class representing TCP socket callback.
class SocketCallback {
public:
/// @brief Implements callback for the asynchronous operation on the socket.
///
/// This callback merely checks if error has occurred and reports this
/// error. It does nothing in case of success.
///
/// @param ec Error code.
/// @param length Length of received data.
void operator()(boost::system::error_code ec, size_t length = 0) {
if (ec) {
ADD_FAILURE() << "error occurred for a socket: " << ec.message();
}
}
};
/// @brief Entity which can connect to the TCP server endpoint and close the
/// connection.
class TCPClient : public boost::noncopyable {
public:
/// @brief Constructor.
///
/// This constructor creates new socket instance. It doesn't connect. Call
/// connect() to connect to the server.
///
/// @param io_service IO service to be stopped on error.
explicit TCPClient(IOService& io_service)
: io_service_(io_service.get_io_service()), socket_(io_service_) {
}
/// @brief Destructor.
///
/// Closes the underlying socket if it is open.
~TCPClient() {
close();
}
/// @brief Connect to the test server address and port.
///
/// This method asynchronously connects to the server endpoint and uses the
/// connectHandler as a callback function.
void connect() {
boost::asio::ip::tcp::endpoint
endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
SERVER_PORT);
socket_.async_connect(endpoint,
boost::bind(&TCPClient::connectHandler, this,_1));
}
/// @brief Callback function for connect().
///
/// This function stops the IO service upon error.
///
/// @param ec Error code.
void connectHandler(const boost::system::error_code& ec) {
if (ec) {
ADD_FAILURE() << "error occurred while connecting: "
<< ec.message();
io_service_.stop();
}
}
/// @brief Close connection.
void close() {
socket_.close();
}
private:
/// @brief Holds reference to the IO service.
boost::asio::io_service& io_service_;
/// @brief A socket used for the connection.
boost::asio::ip::tcp::socket socket_;
};
/// @brief Pointer to the TCPClient.
typedef boost::shared_ptr<TCPClient> TCPClientPtr;
/// @brief A signature of the function implementing callback for the
/// TCPAcceptor.
typedef boost::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
/// @brief TCPAcceptor using TCPAcceptorCallback.
typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
/// @brief Implements asynchronous TCP acceptor service.
///
/// It creates a new socket into which connection is accepted. The socket
/// is retained until class instance exists.
class Acceptor {
public:
/// @brief Constructor.
///
/// @param io_service IO service.
/// @param acceptor Reference to the TCP acceptor on which asyncAccept
/// will be called.
/// @param callback Callback function for the asyncAccept.
explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
const TCPAcceptorCallback& callback)
: socket_(io_service), acceptor_(acceptor), callback_(callback) {
}
/// @brief Destructor.
///
/// Closes socket.
~Acceptor() {
socket_.close();
}
/// @brief Asynchronous accept new connection.
void accept() {
acceptor_.asyncAccept(socket_, callback_);
}
/// @brief Close connection.
void close() {
socket_.close();
}
private:
/// @brief Socket into which connection is accepted.
TCPSocket<SocketCallback> socket_;
/// @brief Reference to the TCPAcceptor on which asyncAccept is called.
TestTCPAcceptor& acceptor_;
/// @brief Instance of the callback used for asyncAccept.
TCPAcceptorCallback callback_;
};
/// @brief Pointer to the Acceptor object.
typedef boost::shared_ptr<Acceptor> AcceptorPtr;
/// @brief Test fixture class for TCPAcceptor.
///
/// This class provides means for creating new TCP connections, i.e. simulates
/// clients connecting to the servers via TCPAcceptor. It is possible to create
/// multiple simultaneous connections, which are retained by the test fixture
/// class and closed cleanly when the test fixture is destroyed.
class TCPAcceptorTest : public ::testing::Test {
public:
/// @brief Constructor.
///
/// Besides initializing class members it also sets the test timer to guard
/// against endlessly running IO service when TCP connections are
/// unsuccessful.
TCPAcceptorTest()
: io_service_(), acceptor_(io_service_),
asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
SERVER_PORT),
endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
clients_(), connections_num_(0), aborted_connections_num_(0),
max_connections_(1) {
test_timer_.setup(boost::bind(&TCPAcceptorTest::timeoutHandler, this),
TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
}
/// @brief Destructor.
virtual ~TCPAcceptorTest() {
}
/// @brief Specifies how many new connections are expected before the IO
/// service is stopped.
///
/// @param max_connections Connections limit.
void setMaxConnections(const unsigned int max_connections) {
max_connections_ = max_connections;
}
/// @brief Create ASIO endpoint from the provided endpoint by retaining the
/// IP address and modifying the port.
///
/// This convenience method is useful to create new endpoint from the
/// existing endpoint to test reusing IP address for multiple acceptors.
/// The returned endpoint has the same IP address but different port.
///
/// @param endpoint Source endpoint.
///
/// @return New endpoint with the port number increased by 1.
boost::asio::ip::tcp::endpoint
createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
endpoint_copy.port(endpoint.port() + 1);
return (endpoint_copy);
}
/// @brief Starts accepting TCP connections.
///
/// This method creates new Acceptor instance and calls accept() to start
/// accepting new connections. The instance of the Acceptor object is
/// retained in the connections_ list.
void accept() {
TCPAcceptorCallback cb = boost::bind(&TCPAcceptorTest::acceptHandler,
this, _1);
AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
connections_.push_back(conn);
connections_.back()->accept();
}
/// @brief Connect to the endpoint.
///
/// This method creates TCPClient instance and retains it in the clients_
/// list.
void connect() {
TCPClientPtr client(new TCPClient(io_service_));
clients_.push_back(client);
clients_.back()->connect();
}
/// @brief Callback function for asynchronous accept calls.
///
/// It stops the IO service upon error or when the number of accepted
/// connections reaches the max_connections_ value. Otherwise it calls
/// accept() to start accepting next connections.
///
/// @param ec Error code.
void acceptHandler(const boost::system::error_code& ec) {
if (ec) {
if (ec.value() != boost::asio::error::operation_aborted) {
ADD_FAILURE() << "error occurred while accepting connection: "
<< ec.message();
} else {
++aborted_connections_num_;
}
io_service_.stop();
}
// We have reached the maximum number of connections - end the test.
if (++connections_num_ >= max_connections_) {
io_service_.stop();
return;
}
accept();
}
/// @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.
IOService io_service_;
/// @brief TCPAcceptor under test.
TestTCPAcceptor acceptor_;
/// @brief Server endpoint.
boost::asio::ip::tcp::endpoint asio_endpoint_;
/// @brief asiolink server endpoint (uses asio_endpoint_).
TCPEndpoint endpoint_;
/// @brief Asynchronous timer service to detect timeouts.
IntervalTimer test_timer_;
/// @brief List of connections on the server side.
std::list<AcceptorPtr> connections_;
/// @brief List of client connections.
std::list<TCPClientPtr> clients_;
/// @brief Current number of established connections.
unsigned int connections_num_;
/// @brief Current number of aborted connections.
unsigned int aborted_connections_num_;
/// @brief Connections limit.
unsigned int max_connections_;
};
// Test TCPAcceptor::asyncAccept.
TEST_F(TCPAcceptorTest, asyncAccept) {
// Establish up to 10 connections.
setMaxConnections(10);
// Initialize acceptor.
acceptor_.open(endpoint_);
acceptor_.bind(endpoint_);
acceptor_.listen();
// Start accepting new connections.
accept();
// Create 10 new TCP connections (client side).
for (unsigned int i = 0; i < 10; ++i) {
connect();
}
// Run the IO service until we have accepted 10 connections, an error
// or test timeout occurred.
io_service_.run();
// Make sure that all accepted connections have been recorded.
EXPECT_EQ(10, connections_num_);
EXPECT_EQ(10, connections_.size());
}
// Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
TEST_F(TCPAcceptorTest, reuseAddress) {
// We need at least two acceptors using common address. Let's create the
// second endpoint which has the same address but different port.
boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
TCPEndpoint endpoint2(asio_endpoint2);
// Create and open two acceptors.
TestTCPAcceptor acceptor1(io_service_);
TestTCPAcceptor acceptor2(io_service_);
ASSERT_NO_THROW(acceptor1.open(endpoint_));
ASSERT_NO_THROW(acceptor2.open(endpoint2));
// Set SO_REUSEADDR socket option so as acceptors can bind to the
/// same address.
ASSERT_NO_THROW(
acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
);
ASSERT_NO_THROW(
acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
);
ASSERT_NO_THROW(acceptor1.bind(endpoint_));
ASSERT_NO_THROW(acceptor2.bind(endpoint2));
// Create third acceptor, but don't set the SO_REUSEADDR. It should
// refuse to bind.
TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
TestTCPAcceptor acceptor3(io_service_);
ASSERT_NO_THROW(acceptor3.open(endpoint3));
EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
}
// Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
TEST_F(TCPAcceptorTest, getProtocol) {
EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
}
// Test that TCPAcceptor::getNative returns valid socket descriptor.
TEST_F(TCPAcceptorTest, getNative) {
// Initially the descriptor should be invalid (negative).
ASSERT_LT(acceptor_.getNative(), 0);
// Now open the socket and make sure the returned descriptor is now valid.