tcp_acceptor_unittest.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. // Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <asiolink/asio_wrapper.h>
  8. #include <asiolink/interval_timer.h>
  9. #include <asiolink/io_address.h>
  10. #include <asiolink/io_service.h>
  11. #include <asiolink/tcp_acceptor.h>
  12. #include <asiolink/tcp_endpoint.h>
  13. #include <boost/bind.hpp>
  14. #include <boost/function.hpp>
  15. #include <boost/noncopyable.hpp>
  16. #include <boost/shared_ptr.hpp>
  17. #include <gtest/gtest.h>
  18. #include <list>
  19. #include <netinet/in.h>
  20. #include <string>
  21. using namespace isc::asiolink;
  22. namespace {
  23. /// @brief Local server address used for testing.
  24. const char SERVER_ADDRESS[] = "127.0.0.1";
  25. /// @brief Local server port used for testing.
  26. const unsigned short SERVER_PORT = 18123;
  27. /// @brief Test timeout in ms.
  28. const long TEST_TIMEOUT = 10000;
  29. /// @brief Simple class representing TCP socket callback.
  30. class SocketCallback {
  31. public:
  32. /// @brief Implements callback for the asynchornous operation on the socket.
  33. ///
  34. /// This callback merely checks if error has occurred and reports this
  35. /// error. It does nothing in case of success.
  36. ///
  37. /// @param ec Error code.
  38. /// @param length Length of received data.
  39. void operator()(boost::system::error_code ec, size_t length = 0) {
  40. if (ec) {
  41. ADD_FAILURE() << "error occurred for a socket: " << ec.message();
  42. }
  43. }
  44. };
  45. /// @brief Entity which can connect to the TCP server endpoint and close the
  46. /// connection.
  47. class TCPClient : public boost::noncopyable {
  48. public:
  49. /// @brief Constructor.
  50. ///
  51. /// This constructor creates new socket instance. It doesn't connect. Call
  52. /// connect() to connect to the server.
  53. ///
  54. /// @param io_service IO service to be stopped on error.
  55. explicit TCPClient(IOService& io_service)
  56. : io_service_(io_service.get_io_service()), socket_(io_service_) {
  57. }
  58. /// @brief Destructor.
  59. ///
  60. /// Closes the underlying socket if it is open.
  61. ~TCPClient() {
  62. close();
  63. }
  64. /// @brief Connect to the test server address and port.
  65. ///
  66. /// This method asynchronously connects to the server endpoint and uses the
  67. /// connectHandler as a callback function.
  68. void connect() {
  69. boost::asio::ip::tcp::endpoint
  70. endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
  71. SERVER_PORT);
  72. socket_.async_connect(endpoint,
  73. boost::bind(&TCPClient::connectHandler, this,_1));
  74. }
  75. /// @brief Callback function for connect().
  76. ///
  77. /// This function stops the IO service upon error.
  78. ///
  79. /// @param ec Error code.
  80. void connectHandler(const boost::system::error_code& ec) {
  81. if (ec) {
  82. ADD_FAILURE() << "error occurred while connecting: "
  83. << ec.message();
  84. io_service_.stop();
  85. }
  86. }
  87. /// @brief Close connection.
  88. void close() {
  89. socket_.close();
  90. }
  91. private:
  92. /// @brief Holds reference to the IO service.
  93. boost::asio::io_service& io_service_;
  94. /// @brief A socket used for the connecion.
  95. boost::asio::ip::tcp::socket socket_;
  96. };
  97. /// @brief Pointer to the TCPClient.
  98. typedef boost::shared_ptr<TCPClient> TCPClientPtr;
  99. /// @brief A signature of the function implementing callback for the
  100. /// TCPAcceptor.
  101. typedef boost::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
  102. /// @brief TCPAcceptor using TCPAcceptorCallback.
  103. typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
  104. /// @brief Implements asynchronous TCP acceptor service.
  105. ///
  106. /// It creates a new socket into which connection is accepted. The socket
  107. /// is retained until class instance exists.
  108. class Acceptor {
  109. public:
  110. /// @brief Constructor.
  111. ///
  112. /// @param io_service IO service.
  113. /// @param acceptor Reference to the TCP acceptor on which asyncAccept
  114. /// will be called.
  115. /// @param callback Callback function for the asyncAccept.
  116. explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
  117. const TCPAcceptorCallback& callback)
  118. : socket_(io_service), acceptor_(acceptor), callback_(callback) {
  119. }
  120. /// @brief Destructor.
  121. ///
  122. /// Closes socket.
  123. ~Acceptor() {
  124. socket_.close();
  125. }
  126. /// @brief Asynchronous accept new connection.
  127. void accept() {
  128. acceptor_.asyncAccept(socket_, callback_);
  129. }
  130. /// @brief Close connection.
  131. void close() {
  132. socket_.close();
  133. }
  134. private:
  135. /// @brief Socket into which connection is accepted.
  136. TCPSocket<SocketCallback> socket_;
  137. /// @brief Reference to the TCPAcceptor on which asyncAccept is called.
  138. TestTCPAcceptor& acceptor_;
  139. /// @brief Instance of the callback used for asyncAccept.
  140. TCPAcceptorCallback callback_;
  141. };
  142. /// @brief Pointer to the Acceptor object.
  143. typedef boost::shared_ptr<Acceptor> AcceptorPtr;
  144. /// @brief Test fixture class for TCPAcceptor.
  145. ///
  146. /// This class provides means for creating new TCP connections, i.e. simulates
  147. /// clients connecting to the servers via TCPAcceptor. It is possible to create
  148. /// multiple simultaneous connections, which are retained by the test fixture
  149. /// class and closed cleanly when the test fixture is destroyed.
  150. class TCPAcceptorTest : public ::testing::Test {
  151. public:
  152. /// @brief Constructor.
  153. ///
  154. /// Besides initializing class members it also sets the test timer to guard
  155. /// against endlessly running IO service when TCP connections are
  156. /// unsuccessful.
  157. TCPAcceptorTest()
  158. : io_service_(), acceptor_(io_service_),
  159. asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
  160. SERVER_PORT),
  161. endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
  162. clients_(), connections_num_(0), max_connections_(1) {
  163. test_timer_.setup(boost::bind(&TCPAcceptorTest::timeoutHandler, this),
  164. TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
  165. }
  166. /// @brief Destructor.
  167. virtual ~TCPAcceptorTest() {
  168. }
  169. /// @brief Specifies how many new connections are expected before the IO
  170. /// service is stopped.
  171. ///
  172. /// @param max_connections Connections limit.
  173. void setMaxConnections(const unsigned int max_connections) {
  174. max_connections_ = max_connections;
  175. }
  176. /// @brief Create ASIO endpoint from the provided endpoint by retaining the
  177. /// IP address and modifying the port.
  178. ///
  179. /// This convenience method is useful to create new endpoint from the
  180. /// existing endpoint to test reusing IP address for multiple acceptors.
  181. /// The returned endpoint has the same IP address but different port.
  182. ///
  183. /// @param endpoint Source endpoint.
  184. ///
  185. /// @return New endpoint with the port number increased by 1.
  186. boost::asio::ip::tcp::endpoint
  187. createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
  188. boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
  189. endpoint_copy.port(endpoint.port() + 1);
  190. return (endpoint_copy);
  191. }
  192. /// @brief Starts accepting TCP connections.
  193. ///
  194. /// This method creates new Acceptor instance and calls accept() to start
  195. /// accepting new connections. The instance of the Acceptor object is
  196. /// retained in the connections_ list.
  197. void accept() {
  198. TCPAcceptorCallback cb = boost::bind(&TCPAcceptorTest::acceptHandler,
  199. this, _1);
  200. AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
  201. connections_.push_back(conn);
  202. connections_.back()->accept();
  203. }
  204. /// @brief Connect to the endpoint.
  205. ///
  206. /// This method creates TCPClient instance and retains it in the clients_
  207. /// list.
  208. void connect() {
  209. TCPClientPtr client(new TCPClient(io_service_));
  210. clients_.push_back(client);
  211. clients_.back()->connect();
  212. }
  213. /// @brief Callback function for asynchronous accept calls.
  214. ///
  215. /// It stops the IO service upon error or when the number of accepted
  216. /// connections reaches the max_connections_ value. Otherwise it calls
  217. /// accept() to start accepting next connections.
  218. ///
  219. /// @param ec Error code.
  220. void acceptHandler(const boost::system::error_code& ec) {
  221. if (ec) {
  222. ADD_FAILURE() << "error occurred while accepting connection: "
  223. << ec.message();
  224. io_service_.stop();
  225. }
  226. // We have reached the maximum number of connections - end the test.
  227. if (++connections_num_ >= max_connections_) {
  228. io_service_.stop();
  229. } else {
  230. accept();
  231. }
  232. }
  233. /// @brief Callback function invoke upon test timeout.
  234. ///
  235. /// It stops the IO service and reports test timeout.
  236. void timeoutHandler() {
  237. ADD_FAILURE() << "Timeout occurred while running the test!";
  238. io_service_.stop();
  239. }
  240. /// @brief IO service.
  241. IOService io_service_;
  242. /// @brief TCPAcceptor under test.
  243. TestTCPAcceptor acceptor_;
  244. /// @brief Server endpoint.
  245. boost::asio::ip::tcp::endpoint asio_endpoint_;
  246. /// @brief asiolink server endpont (uses asio_endpoint_).
  247. TCPEndpoint endpoint_;
  248. /// @brief Asynchronous timer service to detect timeouts.
  249. IntervalTimer test_timer_;
  250. /// @brief List of connections on the server side.
  251. std::list<AcceptorPtr> connections_;
  252. /// @brief List of client connections.
  253. std::list<TCPClientPtr> clients_;
  254. /// @brief Current number of established connections.
  255. unsigned int connections_num_;
  256. /// @brief Connections limit.
  257. unsigned int max_connections_;
  258. };
  259. // Test TCPAcceptor::asyncAccept.
  260. TEST_F(TCPAcceptorTest, asyncAccept) {
  261. // Establish up to 10 connections.
  262. setMaxConnections(10);
  263. // Initialize acceptor.
  264. acceptor_.open(endpoint_);
  265. acceptor_.bind(endpoint_);
  266. acceptor_.listen();
  267. // Start accepting new connections.
  268. accept();
  269. // Create 10 new TCP connections (client side).
  270. for (unsigned int i = 0; i < 10; ++i) {
  271. connect();
  272. }
  273. // Run the IO service until we have accepted 10 connections, an error
  274. // or test timeout occurred.
  275. io_service_.run();
  276. // Make sure that all accepted connections have been recorded.
  277. EXPECT_EQ(10, connections_num_);
  278. EXPECT_EQ(10, connections_.size());
  279. }
  280. // Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
  281. TEST_F(TCPAcceptorTest, reuseAddress) {
  282. // We need at least two acceptors using common address. Let's create the
  283. // second endpoint which has the same address but different port.
  284. boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
  285. TCPEndpoint endpoint2(asio_endpoint2);
  286. // Create and open two acceptors.
  287. TestTCPAcceptor acceptor1(io_service_);
  288. TestTCPAcceptor acceptor2(io_service_);
  289. ASSERT_NO_THROW(acceptor1.open(endpoint_));
  290. ASSERT_NO_THROW(acceptor2.open(endpoint2));
  291. // Set SO_REUSEADDR socket option so as acceptors can bind to the
  292. /// same address.
  293. ASSERT_NO_THROW(
  294. acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
  295. );
  296. ASSERT_NO_THROW(
  297. acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
  298. );
  299. ASSERT_NO_THROW(acceptor1.bind(endpoint_));
  300. ASSERT_NO_THROW(acceptor2.bind(endpoint2));
  301. // Create third acceptor, but don't set the SO_REUSEADDR. It should
  302. // refuse to bind.
  303. TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
  304. TestTCPAcceptor acceptor3(io_service_);
  305. ASSERT_NO_THROW(acceptor3.open(endpoint3));
  306. EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
  307. }
  308. // Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
  309. TEST_F(TCPAcceptorTest, getProtocol) {
  310. EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
  311. }
  312. // Test that TCPAcceptor::getNative returns valid socket descriptor.
  313. TEST_F(TCPAcceptorTest, getNative) {
  314. // Initially the descriptor should be invalid (negative).
  315. ASSERT_LT(acceptor_.getNative(), 0);
  316. // Now open the socket and make sure the returned descriptor is now valid.
  317. ASSERT_NO_THROW(acceptor_.open(endpoint_));
  318. EXPECT_GE(acceptor_.getNative(), 0);
  319. }
  320. }