tcp_acceptor_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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 asynchronous 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. // One would expect that async_connect wouldn't return EINPROGRESS
  83. // error code, but simply wait for the connection to get
  84. // established before the handler is invoked. It turns out, however,
  85. // that on some OSes the connect handler may receive this error code
  86. // which doesn't necessarily indicate a problem. Making an attempt
  87. // to write and read from this socket will typically succeed. So,
  88. // we ignore this error.
  89. if (ec.value() != boost::asio::error::in_progress) {
  90. ADD_FAILURE() << "error occurred while connecting: "
  91. << ec.message();
  92. io_service_.stop();
  93. }
  94. }
  95. }
  96. /// @brief Close connection.
  97. void close() {
  98. socket_.close();
  99. }
  100. private:
  101. /// @brief Holds reference to the IO service.
  102. boost::asio::io_service& io_service_;
  103. /// @brief A socket used for the connection.
  104. boost::asio::ip::tcp::socket socket_;
  105. };
  106. /// @brief Pointer to the TCPClient.
  107. typedef boost::shared_ptr<TCPClient> TCPClientPtr;
  108. /// @brief A signature of the function implementing callback for the
  109. /// TCPAcceptor.
  110. typedef boost::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
  111. /// @brief TCPAcceptor using TCPAcceptorCallback.
  112. typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
  113. /// @brief Implements asynchronous TCP acceptor service.
  114. ///
  115. /// It creates a new socket into which connection is accepted. The socket
  116. /// is retained until class instance exists.
  117. class Acceptor {
  118. public:
  119. /// @brief Constructor.
  120. ///
  121. /// @param io_service IO service.
  122. /// @param acceptor Reference to the TCP acceptor on which asyncAccept
  123. /// will be called.
  124. /// @param callback Callback function for the asyncAccept.
  125. explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
  126. const TCPAcceptorCallback& callback)
  127. : socket_(io_service), acceptor_(acceptor), callback_(callback) {
  128. }
  129. /// @brief Destructor.
  130. ///
  131. /// Closes socket.
  132. ~Acceptor() {
  133. socket_.close();
  134. }
  135. /// @brief Asynchronous accept new connection.
  136. void accept() {
  137. acceptor_.asyncAccept(socket_, callback_);
  138. }
  139. /// @brief Close connection.
  140. void close() {
  141. socket_.close();
  142. }
  143. private:
  144. /// @brief Socket into which connection is accepted.
  145. TCPSocket<SocketCallback> socket_;
  146. /// @brief Reference to the TCPAcceptor on which asyncAccept is called.
  147. TestTCPAcceptor& acceptor_;
  148. /// @brief Instance of the callback used for asyncAccept.
  149. TCPAcceptorCallback callback_;
  150. };
  151. /// @brief Pointer to the Acceptor object.
  152. typedef boost::shared_ptr<Acceptor> AcceptorPtr;
  153. /// @brief Test fixture class for TCPAcceptor.
  154. ///
  155. /// This class provides means for creating new TCP connections, i.e. simulates
  156. /// clients connecting to the servers via TCPAcceptor. It is possible to create
  157. /// multiple simultaneous connections, which are retained by the test fixture
  158. /// class and closed cleanly when the test fixture is destroyed.
  159. class TCPAcceptorTest : public ::testing::Test {
  160. public:
  161. /// @brief Constructor.
  162. ///
  163. /// Besides initializing class members it also sets the test timer to guard
  164. /// against endlessly running IO service when TCP connections are
  165. /// unsuccessful.
  166. TCPAcceptorTest()
  167. : io_service_(), acceptor_(io_service_),
  168. asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
  169. SERVER_PORT),
  170. endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
  171. clients_(), connections_num_(0), aborted_connections_num_(0),
  172. max_connections_(1) {
  173. test_timer_.setup(boost::bind(&TCPAcceptorTest::timeoutHandler, this),
  174. TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
  175. }
  176. /// @brief Destructor.
  177. virtual ~TCPAcceptorTest() {
  178. }
  179. /// @brief Specifies how many new connections are expected before the IO
  180. /// service is stopped.
  181. ///
  182. /// @param max_connections Connections limit.
  183. void setMaxConnections(const unsigned int max_connections) {
  184. max_connections_ = max_connections;
  185. }
  186. /// @brief Create ASIO endpoint from the provided endpoint by retaining the
  187. /// IP address and modifying the port.
  188. ///
  189. /// This convenience method is useful to create new endpoint from the
  190. /// existing endpoint to test reusing IP address for multiple acceptors.
  191. /// The returned endpoint has the same IP address but different port.
  192. ///
  193. /// @param endpoint Source endpoint.
  194. ///
  195. /// @return New endpoint with the port number increased by 1.
  196. boost::asio::ip::tcp::endpoint
  197. createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
  198. boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
  199. endpoint_copy.port(endpoint.port() + 1);
  200. return (endpoint_copy);
  201. }
  202. /// @brief Opens TCP acceptor and sets 'reuse address' option.
  203. void acceptorOpen() {
  204. acceptor_.open(endpoint_);
  205. acceptor_.setOption(TestTCPAcceptor::ReuseAddress(true));
  206. }
  207. /// @brief Starts accepting TCP connections.
  208. ///
  209. /// This method creates new Acceptor instance and calls accept() to start
  210. /// accepting new connections. The instance of the Acceptor object is
  211. /// retained in the connections_ list.
  212. void accept() {
  213. TCPAcceptorCallback cb = boost::bind(&TCPAcceptorTest::acceptHandler,
  214. this, _1);
  215. AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
  216. connections_.push_back(conn);
  217. connections_.back()->accept();
  218. }
  219. /// @brief Connect to the endpoint.
  220. ///
  221. /// This method creates TCPClient instance and retains it in the clients_
  222. /// list.
  223. void connect() {
  224. TCPClientPtr client(new TCPClient(io_service_));
  225. clients_.push_back(client);
  226. clients_.back()->connect();
  227. }
  228. /// @brief Callback function for asynchronous accept calls.
  229. ///
  230. /// It stops the IO service upon error or when the number of accepted
  231. /// connections reaches the max_connections_ value. Otherwise it calls
  232. /// accept() to start accepting next connections.
  233. ///
  234. /// @param ec Error code.
  235. void acceptHandler(const boost::system::error_code& ec) {
  236. if (ec) {
  237. if (ec.value() != boost::asio::error::operation_aborted) {
  238. ADD_FAILURE() << "error occurred while accepting connection: "
  239. << ec.message();
  240. } else {
  241. ++aborted_connections_num_;
  242. }
  243. io_service_.stop();
  244. }
  245. // We have reached the maximum number of connections - end the test.
  246. if (++connections_num_ >= max_connections_) {
  247. io_service_.stop();
  248. return;
  249. }
  250. accept();
  251. }
  252. /// @brief Callback function invoke upon test timeout.
  253. ///
  254. /// It stops the IO service and reports test timeout.
  255. void timeoutHandler() {
  256. ADD_FAILURE() << "Timeout occurred while running the test!";
  257. io_service_.stop();
  258. }
  259. /// @brief IO service.
  260. IOService io_service_;
  261. /// @brief TCPAcceptor under test.
  262. TestTCPAcceptor acceptor_;
  263. /// @brief Server endpoint.
  264. boost::asio::ip::tcp::endpoint asio_endpoint_;
  265. /// @brief asiolink server endpoint (uses asio_endpoint_).
  266. TCPEndpoint endpoint_;
  267. /// @brief Asynchronous timer service to detect timeouts.
  268. IntervalTimer test_timer_;
  269. /// @brief List of connections on the server side.
  270. std::list<AcceptorPtr> connections_;
  271. /// @brief List of client connections.
  272. std::list<TCPClientPtr> clients_;
  273. /// @brief Current number of established connections.
  274. unsigned int connections_num_;
  275. /// @brief Current number of aborted connections.
  276. unsigned int aborted_connections_num_;
  277. /// @brief Connections limit.
  278. unsigned int max_connections_;
  279. };
  280. // Test TCPAcceptor::asyncAccept.
  281. TEST_F(TCPAcceptorTest, asyncAccept) {
  282. // Establish up to 10 connections.
  283. setMaxConnections(10);
  284. // Initialize acceptor.
  285. acceptorOpen();
  286. acceptor_.bind(endpoint_);
  287. acceptor_.listen();
  288. // Start accepting new connections.
  289. accept();
  290. // Create 10 new TCP connections (client side).
  291. for (unsigned int i = 0; i < 10; ++i) {
  292. connect();
  293. }
  294. // Run the IO service until we have accepted 10 connections, an error
  295. // or test timeout occurred.
  296. io_service_.run();
  297. // Make sure that all accepted connections have been recorded.
  298. EXPECT_EQ(10, connections_num_);
  299. EXPECT_EQ(10, connections_.size());
  300. }
  301. // Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
  302. TEST_F(TCPAcceptorTest, reuseAddress) {
  303. // We need at least two acceptors using common address. Let's create the
  304. // second endpoint which has the same address but different port.
  305. boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
  306. TCPEndpoint endpoint2(asio_endpoint2);
  307. // Create and open two acceptors.
  308. TestTCPAcceptor acceptor1(io_service_);
  309. TestTCPAcceptor acceptor2(io_service_);
  310. ASSERT_NO_THROW(acceptor1.open(endpoint_));
  311. ASSERT_NO_THROW(acceptor2.open(endpoint2));
  312. // Set SO_REUSEADDR socket option so as acceptors can bind to the
  313. /// same address.
  314. ASSERT_NO_THROW(
  315. acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
  316. );
  317. ASSERT_NO_THROW(
  318. acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
  319. );
  320. ASSERT_NO_THROW(acceptor1.bind(endpoint_));
  321. ASSERT_NO_THROW(acceptor2.bind(endpoint2));
  322. // Create third acceptor, but don't set the SO_REUSEADDR. It should
  323. // refuse to bind.
  324. TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
  325. TestTCPAcceptor acceptor3(io_service_);
  326. ASSERT_NO_THROW(acceptor3.open(endpoint3));
  327. EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
  328. }
  329. // Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
  330. TEST_F(TCPAcceptorTest, getProtocol) {
  331. EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
  332. }
  333. // Test that TCPAcceptor::getNative returns valid socket descriptor.
  334. TEST_F(TCPAcceptorTest, getNative) {
  335. // Initially the descriptor should be invalid (negative).
  336. ASSERT_LT(acceptor_.getNative(), 0);
  337. // Now open the socket and make sure the returned descriptor is now valid.
  338. ASSERT_NO_THROW(acceptorOpen());
  339. EXPECT_GE(acceptor_.getNative(), 0);
  340. }
  341. // macOS 10.12.3 has a bug which causes the connections to not enter
  342. // the TIME-WAIT state and they never get closed.
  343. #if !defined (OS_OSX)
  344. // Test that TCPAcceptor::close works properly.
  345. TEST_F(TCPAcceptorTest, close) {
  346. // Initialize acceptor.
  347. acceptorOpen();
  348. acceptor_.bind(endpoint_);
  349. acceptor_.listen();
  350. // Start accepting new connections.
  351. accept();
  352. // Create 10 new TCP connections (client side).
  353. for (unsigned int i = 0; i < 10; ++i) {
  354. connect();
  355. }
  356. // Close the acceptor before connections are accepted.
  357. acceptor_.close();
  358. // Run the IO service.
  359. io_service_.run();
  360. // The connections should have been aborted.
  361. EXPECT_EQ(1, connections_num_);
  362. EXPECT_EQ(1, aborted_connections_num_);
  363. EXPECT_EQ(1, connections_.size());
  364. }
  365. #endif
  366. }