123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- // 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 asynchornous 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 connecion.
- 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), 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) {
- ADD_FAILURE() << "error occurred while accepting connection: "
- << ec.message();
- io_service_.stop();
- }
- // We have reached the maximum number of connections - end the test.
- if (++connections_num_ >= max_connections_) {
- io_service_.stop();
- } else {
- 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 endpont (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 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.
- ASSERT_NO_THROW(acceptor_.open(endpoint_));
- EXPECT_GE(acceptor_.getNative(), 0);
- }
- }
|