// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. // $Id$ #include #include #include #include #include #include #include #include #include #include #include #include // IMPORTANT: We shouldn't directly use ASIO definitions in this test. // In particular, we must not include asio.hpp in this file. // The asiolink module is primarily intended to be a wrapper that hide the // details of the underlying implementations. We need to test the wrapper // level behaviors. In addition, some compilers reject to compile this file // if we include asio.hpp unless we specify a special compiler option. // If we need to test something at the level of underlying ASIO and need // their definition, that test should go to asiolink/internal/tests. #include #include using isc::UnitTestUtil; using namespace std; using namespace asiolink; using namespace isc::dns; namespace { const char* const TEST_SERVER_PORT = "53535"; const char* const TEST_CLIENT_PORT = "53536"; const char* const TEST_IPV6_ADDR = "::1"; const char* const TEST_IPV4_ADDR = "127.0.0.1"; // This data is intended to be valid as a DNS/TCP-like message: the first // two octets encode the length of the rest of the data. This is crucial // for the tests below. const uint8_t test_data[] = {0, 4, 1, 2, 3, 4}; // TODO: Consider this margin const boost::posix_time::time_duration TIMER_MARGIN_MSEC = boost::posix_time::milliseconds(50); TEST(IOAddressTest, fromText) { IOAddress io_address_v4("192.0.2.1"); EXPECT_EQ("192.0.2.1", io_address_v4.toText()); IOAddress io_address_v6("2001:db8::1234"); EXPECT_EQ("2001:db8::1234", io_address_v6.toText()); // bogus IPv4 address-like input EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError); // bogus IPv4 address-like input: out-of-range octet EXPECT_THROW(IOAddress("192.0.2.300"), IOError); // bogus IPv6 address-like input EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError); // bogus IPv6 address-like input EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError); } TEST(IOEndpointTest, createUDPv4) { const IOEndpoint* ep; ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300); EXPECT_EQ("192.0.2.1", ep->getAddress().toText()); EXPECT_EQ(5300, ep->getPort()); EXPECT_EQ(AF_INET, ep->getFamily()); EXPECT_EQ(AF_INET, ep->getAddress().getFamily()); EXPECT_EQ(IPPROTO_UDP, ep->getProtocol()); } TEST(IOEndpointTest, createTCPv4) { const IOEndpoint* ep; ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301); EXPECT_EQ("192.0.2.1", ep->getAddress().toText()); EXPECT_EQ(5301, ep->getPort()); EXPECT_EQ(AF_INET, ep->getFamily()); EXPECT_EQ(AF_INET, ep->getAddress().getFamily()); EXPECT_EQ(IPPROTO_TCP, ep->getProtocol()); } TEST(IOEndpointTest, createUDPv6) { const IOEndpoint* ep; ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302); EXPECT_EQ("2001:db8::1234", ep->getAddress().toText()); EXPECT_EQ(5302, ep->getPort()); EXPECT_EQ(AF_INET6, ep->getFamily()); EXPECT_EQ(AF_INET6, ep->getAddress().getFamily()); EXPECT_EQ(IPPROTO_UDP, ep->getProtocol()); } TEST(IOEndpointTest, createTCPv6) { const IOEndpoint* ep; ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303); EXPECT_EQ("2001:db8::1234", ep->getAddress().toText()); EXPECT_EQ(5303, ep->getPort()); EXPECT_EQ(AF_INET6, ep->getFamily()); EXPECT_EQ(AF_INET6, ep->getAddress().getFamily()); EXPECT_EQ(IPPROTO_TCP, ep->getProtocol()); } TEST(IOEndpointTest, createIPProto) { EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"), 5300)->getAddress().toText(), IOError); } TEST(IOSocketTest, dummySockets) { EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol()); EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol()); EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative()); EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative()); } TEST(IOServiceTest, badPort) { IOService io_service; EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *"5300.0", true, false, NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError); } TEST(IOServiceTest, badAddress) { IOService io_service; EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError); } TEST(IOServiceTest, unavailableAddress) { IOService io_service; // These addresses should generally be unavailable as a valid local // address, although there's no guarantee in theory. EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"255.255.0.0", NULL, NULL, NULL), IOError); // Some OSes would simply reject binding attempt for an AF_INET6 socket // to an IPv4-mapped IPv6 address. Even if those that allow it, since // the corresponding IPv4 address is the same as the one used in the // AF_INET socket case above, it should at least show the same result // as the previous one. EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:255.255.0.0", NULL, NULL, NULL), IOError); } TEST(IOServiceTest, duplicateBind_v6) { // In each sub test case, second attempt should fail due to duplicate bind IOService io_service; // IPv6, "any" address DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError); delete dns_service; } TEST(IOServiceTest, duplicateBind_v6_address) { // In each sub test case, second attempt should fail due to duplicate bind IOService io_service; // IPv6, specific address DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError); delete dns_service; } TEST(IOServiceTest, duplicateBind_v4) { // In each sub test case, second attempt should fail due to duplicate bind IOService io_service; // IPv4, "any" address DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError); delete dns_service; } TEST(IOServiceTest, duplicateBind_v4_address) { // In each sub test case, second attempt should fail due to duplicate bind IOService io_service; // IPv4, specific address DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError); delete dns_service; } // Disabled because IPv4-mapped addresses don't seem to be working with // the IOService constructor TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) { IOService io_service; // Duplicate bind on IPv4-mapped IPv6 address DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError); delete dns_service; // XXX: // Currently, this throws an "invalid argument" exception. I have // not been able to get IPv4-mapped addresses to work. dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL); EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError); delete dns_service; } // This function returns an addrinfo structure for use by tests, using // different addresses and ports depending on whether we're testing // IPv4 or v6, TCP or UDP, and client or server operation. struct addrinfo* resolveAddress(const int family, const int protocol, const bool client) { const char* const addr = (family == AF_INET6) ? TEST_IPV6_ADDR : TEST_IPV4_ADDR; const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM; hints.ai_protocol = protocol; hints.ai_flags = AI_NUMERICSERV; struct addrinfo* res; const int error = getaddrinfo(addr, port, &hints, &res); if (error != 0) { isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error)); } return (res); } // This fixture is a framework for various types of network operations // using the ASIO interfaces. Each test case creates an IOService object, // opens a local "client" socket for testing, sends data via the local socket // to the service that would run in the IOService object. // A mock callback function (an ASIOCallBack object) is registered with the // IOService object, so the test code should be able to examine the data // received on the server side. It then checks the received data matches // expected parameters. // If initialization parameters of the IOService should be modified, the test // case can do it using the setDNSService() method. // Note: the set of tests in ASIOLinkTest use actual network services and may // involve undesirable side effects such as blocking. class ASIOLinkTest : public ::testing::Test { protected: ASIOLinkTest(); ~ASIOLinkTest() { if (res_ != NULL) { freeaddrinfo(res_); } if (sock_ != -1) { close(sock_); } delete dns_service_; delete callback_; delete io_service_; } // Send a test UDP packet to a mock server void sendUDP(const int family) { res_ = resolveAddress(family, IPPROTO_UDP, false); sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol); if (sock_ < 0) { isc_throw(IOError, "failed to open test socket"); } const int cc = sendto(sock_, test_data, sizeof(test_data), 0, res_->ai_addr, res_->ai_addrlen); if (cc != sizeof(test_data)) { isc_throw(IOError, "unexpected sendto result: " << cc); } io_service_->run(); } // Send a test TCP packet to a mock server void sendTCP(const int family) { res_ = resolveAddress(family, IPPROTO_TCP, false); sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol); if (sock_ < 0) { isc_throw(IOError, "failed to open test socket"); } if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) { isc_throw(IOError, "failed to connect to the test server"); } const int cc = send(sock_, test_data, sizeof(test_data), 0); if (cc != sizeof(test_data)) { isc_throw(IOError, "unexpected send result: " << cc); } io_service_->run(); } // Receive a UDP packet from a mock server; used for testing // recursive lookup. The caller must place a RecursiveQuery // on the IO Service queue before running this routine. void recvUDP(const int family, void* buffer, size_t& size) { res_ = resolveAddress(family, IPPROTO_UDP, true); sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol); if (sock_ < 0) { isc_throw(IOError, "failed to open test socket"); } if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) { isc_throw(IOError, "bind failed: " << strerror(errno)); } // The IO service queue should have a RecursiveQuery object scheduled // to run at this point. This call will cause it to begin an // async send, then return. io_service_->run_one(); // ... and this one will block until the send has completed io_service_->run_one(); // Now we attempt to recv() whatever was sent. // XXX: there's no guarantee the receiving socket can immediately get // the packet. Normally we can perform blocking recv to wait for it, // but in theory it's even possible that the packet is lost. // In order to prevent the test from hanging in such a worst case // we add an ad hoc timeout. const struct timeval timeo = { 10, 0 }; int recv_options = 0; if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) { if (errno == ENOPROTOOPT) { // Workaround for Solaris: it doesn't accept SO_RCVTIMEO // with the error of ENOPROTOOPT. Since this is a workaround // for rare error cases anyway, we simply switch to the // "don't wait" mode. If we still find an error in recv() // can happen often we'll consider a more complete solution. recv_options = MSG_DONTWAIT; } else { isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno)); } } const int ret = recv(sock_, buffer, size, recv_options); if (ret < 0) { isc_throw(IOError, "recvfrom failed: " << strerror(errno)); } // Pass the message size back via the size parameter size = ret; } // Set up an IO Service queue using the specified address void setDNSService(const char& address) { delete dns_service_; dns_service_ = NULL; delete io_service_; io_service_ = new IOService(); callback_ = new ASIOCallBack(this); dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL); } // Set up an IO Service queue using the "any" address, on IPv4 if // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true. void setDNSService(const bool use_ipv4, const bool use_ipv6) { delete dns_service_; dns_service_ = NULL; delete io_service_; io_service_ = new IOService(); callback_ = new ASIOCallBack(this); dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_, NULL, NULL); } // Set up empty DNS Service // Set up an IO Service queue without any addresses void setDNSService() { delete dns_service_; dns_service_ = NULL; delete io_service_; io_service_ = new IOService(); callback_ = new ASIOCallBack(this); dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL); } // Run a simple server test, on either IPv4 or IPv6, and over either // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will // start the IO Service queue. The UDPServer or TCPServer that was // created by setIOService() will receive the test packet and issue a // callback, which enables us to check that the data it received // matches what we sent. void doTest(const int family, const int protocol) { if (protocol == IPPROTO_UDP) { sendUDP(family); } else { sendTCP(family); } // There doesn't seem to be an effective test for the validity of // 'native'. // One thing we are sure is it must be different from our local socket. EXPECT_NE(sock_, callback_native_); EXPECT_EQ(protocol, callback_protocol_); EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR, callback_address_); const uint8_t* expected_data = protocol == IPPROTO_UDP ? test_data : test_data + 2; const size_t expected_datasize = protocol == IPPROTO_UDP ? sizeof(test_data) : sizeof(test_data) - 2; EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0], callback_data_.size(), expected_data, expected_datasize); } protected: // This is a nonfunctional mockup of a DNSServer object. Its purpose // is to resume after a recursive query or other asynchronous call // has completed. class MockServer : public DNSServer { public: explicit MockServer(IOService& io_service, SimpleCallback* checkin = NULL, DNSLookup* lookup = NULL, DNSAnswer* answer = NULL) : io_(io_service), message_(new Message(Message::PARSE)), respbuf_(new OutputBuffer(0)), checkin_(checkin), lookup_(lookup), answer_(answer) {} void operator()(asio::error_code ec = asio::error_code(), size_t length = 0) {} void resume(const bool) { // in our test this shouldn't be called } DNSServer* clone() { MockServer* s = new MockServer(*this); return (s); } inline void asyncLookup() { if (lookup_) { (*lookup_)(*io_message_, message_, respbuf_, this); } } protected: IOService& io_; bool done_; private: // Currently unused; these will be used for testing // asynchronous lookup calls via the asyncLookup() method boost::shared_ptr io_message_; isc::dns::MessagePtr message_; isc::dns::OutputBufferPtr respbuf_; // Callback functions provided by the caller const SimpleCallback* checkin_; const DNSLookup* lookup_; const DNSAnswer* answer_; }; // This version of mock server just stops the io_service when it is resumed class MockServerStop : public MockServer { public: explicit MockServerStop(IOService& io_service, bool* done) : MockServer(io_service), done_(done) {} void resume(const bool done) { *done_ = done; io_.stop(); } DNSServer* clone() { return (new MockServerStop(*this)); } private: bool* done_; }; private: class ASIOCallBack : public SimpleCallback { public: ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {} void operator()(const IOMessage& io_message) const { test_obj_->callBack(io_message); } private: ASIOLinkTest* test_obj_; }; void callBack(const IOMessage& io_message) { callback_protocol_ = io_message.getSocket().getProtocol(); callback_native_ = io_message.getSocket().getNative(); callback_address_ = io_message.getRemoteEndpoint().getAddress().toText(); callback_data_.assign( static_cast(io_message.getData()), static_cast(io_message.getData()) + io_message.getDataSize()); io_service_->stop(); } protected: // We use a pointer for io_service_, because for some tests we // need to recreate a new one within one onstance of this class IOService* io_service_; DNSService* dns_service_; ASIOCallBack* callback_; int callback_protocol_; int callback_native_; string callback_address_; vector callback_data_; int sock_; struct addrinfo* res_; }; ASIOLinkTest::ASIOLinkTest() : dns_service_(NULL), callback_(NULL), sock_(-1), res_(NULL) { io_service_ = new IOService(); setDNSService(true, true); } TEST_F(ASIOLinkTest, v6UDPSend) { doTest(AF_INET6, IPPROTO_UDP); } TEST_F(ASIOLinkTest, v6TCPSend) { doTest(AF_INET6, IPPROTO_TCP); } TEST_F(ASIOLinkTest, v4UDPSend) { doTest(AF_INET, IPPROTO_UDP); } TEST_F(ASIOLinkTest, v4TCPSend) { doTest(AF_INET, IPPROTO_TCP); } TEST_F(ASIOLinkTest, v6UDPSendSpecific) { // Explicitly set a specific address to be bound to the socket. // The subsequent test does not directly ensures the underlying socket // is bound to the expected address, but the success of the tests should // reasonably suggest it works as intended. // Specifying an address also implicitly means the service runs in a // single address-family mode. In tests using TCP we can confirm that // by trying to make a connection and seeing a failure. In UDP, it'd be // more complicated because we need to use a connected socket and catch // an error on a subsequent read operation. We could do it, but for // simplicity we only tests the easier cases for now. setDNSService(*TEST_IPV6_ADDR); doTest(AF_INET6, IPPROTO_UDP); } TEST_F(ASIOLinkTest, v6TCPSendSpecific) { setDNSService(*TEST_IPV6_ADDR); doTest(AF_INET6, IPPROTO_TCP); EXPECT_THROW(sendTCP(AF_INET), IOError); } TEST_F(ASIOLinkTest, v4UDPSendSpecific) { setDNSService(*TEST_IPV4_ADDR); doTest(AF_INET, IPPROTO_UDP); } TEST_F(ASIOLinkTest, v4TCPSendSpecific) { setDNSService(*TEST_IPV4_ADDR); doTest(AF_INET, IPPROTO_TCP); EXPECT_THROW(sendTCP(AF_INET6), IOError); } TEST_F(ASIOLinkTest, v6AddServer) { setDNSService(); dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR); doTest(AF_INET6, IPPROTO_TCP); EXPECT_THROW(sendTCP(AF_INET), IOError); } TEST_F(ASIOLinkTest, v4AddServer) { setDNSService(); dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR); doTest(AF_INET, IPPROTO_TCP); EXPECT_THROW(sendTCP(AF_INET6), IOError); } TEST_F(ASIOLinkTest, DISABLED_clearServers) { // FIXME: Enable when clearServers actually close the sockets // See #388 setDNSService(); dns_service_->clearServers(); EXPECT_THROW(sendTCP(AF_INET), IOError); EXPECT_THROW(sendTCP(AF_INET6), IOError); } TEST_F(ASIOLinkTest, v6TCPOnly) { // Open only IPv6 TCP socket. A subsequent attempt of establishing an // IPv4/TCP connection should fail. See above for why we only test this // for TCP. setDNSService(false, true); EXPECT_THROW(sendTCP(AF_INET), IOError); } TEST_F(ASIOLinkTest, v4TCPOnly) { setDNSService(true, false); EXPECT_THROW(sendTCP(AF_INET6), IOError); } vector > singleAddress(const string &address, uint16_t port) { vector > result; result.push_back(pair(address, port)); return (result); } TEST_F(ASIOLinkTest, recursiveSetupV4) { setDNSService(true, false); uint16_t port = boost::lexical_cast(TEST_CLIENT_PORT); EXPECT_NO_THROW(RecursiveQuery(*dns_service_, singleAddress(TEST_IPV6_ADDR, port))); } TEST_F(ASIOLinkTest, recursiveSetupV6) { setDNSService(false, true); uint16_t port = boost::lexical_cast(TEST_CLIENT_PORT); EXPECT_NO_THROW(RecursiveQuery(*dns_service_, singleAddress(TEST_IPV6_ADDR, port))); } // XXX: // This is very inadequate unit testing. It should be generalized into // a routine that can do this with variable address family, address, and // port, and with the various callbacks defined in such a way as to ensure // full code coverage including error cases. TEST_F(ASIOLinkTest, recursiveSend) { setDNSService(true, false); // Note: We use the test prot plus one to ensure we aren't binding // to the same port as the actual server uint16_t port = boost::lexical_cast(TEST_CLIENT_PORT); MockServer server(*io_service_); RecursiveQuery rq(*dns_service_, singleAddress(TEST_IPV4_ADDR, port)); Question q(Name("example.com"), RRClass::IN(), RRType::TXT()); OutputBufferPtr buffer(new OutputBuffer(0)); rq.sendQuery(q, buffer, &server); char data[4096]; size_t size = sizeof(data); ASSERT_NO_THROW(recvUDP(AF_INET, data, size)); Message m(Message::PARSE); InputBuffer ibuf(data, size); // Make sure we can parse the message that was sent EXPECT_NO_THROW(m.parseHeader(ibuf)); EXPECT_NO_THROW(m.fromWire(ibuf)); // Check that the question sent matches the one we wanted QuestionPtr q2 = *m.beginQuestion(); EXPECT_EQ(q.getName(), q2->getName()); EXPECT_EQ(q.getType(), q2->getType()); EXPECT_EQ(q.getClass(), q2->getClass()); } // Test it tries the correct amount of times before giving up TEST_F(ASIOLinkTest, recursiveTimeout) { // Prepare the service (we do not use the common setup, we do not answer setDNSService(); // Prepare the socket res_ = resolveAddress(AF_INET, IPPROTO_UDP, true); sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol); if (sock_ < 0) { isc_throw(IOError, "failed to open test socket"); } if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) { isc_throw(IOError, "failed to bind test socket"); } // Prepare the server bool done(true); MockServerStop server(*io_service_, &done); // Do the answer const uint16_t port = boost::lexical_cast(TEST_CLIENT_PORT); RecursiveQuery query(*dns_service_, singleAddress(TEST_IPV4_ADDR, port), 10, 2); Question question(Name("example.net"), RRClass::IN(), RRType::A()); OutputBufferPtr buffer(new OutputBuffer(0)); query.sendQuery(question, buffer, &server); // Run the test io_service_->run(); // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite // block (see also recvUDP()). const struct timeval timeo = { 10, 0 }; int recv_options = 0; if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) { if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP() recv_options = MSG_DONTWAIT; } else { isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno)); } } int num = 0; do { char inbuff[512]; if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) { num = -1; break; } } while (++num < 3); // The query should fail EXPECT_FALSE(done); EXPECT_EQ(3, num); } // This fixture is for testing IntervalTimer. Some callback functors are // registered as callback function of the timer to test if they are called // or not. class IntervalTimerTest : public ::testing::Test { protected: IntervalTimerTest() : io_service_() {}; ~IntervalTimerTest() {} class TimerCallBack : public std::unary_function { public: TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {} void operator()() const { test_obj_->timer_called_ = true; test_obj_->io_service_.stop(); return; } private: IntervalTimerTest* test_obj_; }; class TimerCallBackCounter : public std::unary_function { public: TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) { counter_ = 0; } void operator()() { ++counter_; return; } int counter_; private: IntervalTimerTest* test_obj_; }; class TimerCallBackCancelDeleter : public std::unary_function { public: TimerCallBackCancelDeleter(IntervalTimerTest* test_obj, IntervalTimer* timer, TimerCallBackCounter& counter) : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0) {} void operator()() { ++count_; if (count_ == 1) { // First time of call back. // Store the value of counter_.counter_. prev_counter_ = counter_.counter_; delete timer_; } else if (count_ == 2) { // Second time of call back. // Stop io_service to stop all timers. test_obj_->io_service_.stop(); // Compare the value of counter_.counter_ with stored one. // If TimerCallBackCounter was not called (expected behavior), // they are same. if (counter_.counter_ == prev_counter_) { test_obj_->timer_cancel_success_ = true; } } return; } private: IntervalTimerTest* test_obj_; IntervalTimer* timer_; TimerCallBackCounter& counter_; int count_; int prev_counter_; }; class TimerCallBackOverwriter : public std::unary_function { public: TimerCallBackOverwriter(IntervalTimerTest* test_obj, IntervalTimer& timer) : test_obj_(test_obj), timer_(timer), count_(0) {} void operator()() { ++count_; if (count_ == 1) { // First time of call back. // Call setupTimer() to update callback function // to TimerCallBack. test_obj_->timer_called_ = false; timer_.setupTimer(TimerCallBack(test_obj_), 1); } else if (count_ == 2) { // Second time of call back. // If it reaches here, re-setupTimer() is failed (unexpected). // We should stop here. test_obj_->io_service_.stop(); } return; } private: IntervalTimerTest* test_obj_; IntervalTimer& timer_; int count_; }; protected: IOService io_service_; bool timer_called_; bool timer_cancel_success_; }; TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) { // Create asio_link::IntervalTimer and setup. IntervalTimer itimer(io_service_); // expect throw if call back function is empty EXPECT_THROW(itimer.setupTimer(IntervalTimer::Callback(), 1), isc::InvalidParameter); // expect throw if interval is 0 EXPECT_THROW(itimer.setupTimer(TimerCallBack(this), 0), isc::BadValue); } TEST_F(IntervalTimerTest, startIntervalTimer) { // Create asio_link::IntervalTimer and setup. // Then run IOService and test if the callback function is called. IntervalTimer itimer(io_service_); timer_called_ = false; // store start time boost::posix_time::ptime start; start = boost::posix_time::microsec_clock::universal_time(); // setup timer itimer.setupTimer(TimerCallBack(this), 1); io_service_.run(); // reaches here after timer expired // delta: difference between elapsed time and 1 second boost::posix_time::time_duration delta = (boost::posix_time::microsec_clock::universal_time() - start) - boost::posix_time::seconds(1); if (delta.is_negative()) { delta.invert_sign(); } // expect TimerCallBack is called; timer_called_ is true EXPECT_TRUE(timer_called_); // expect interval is 1 second +/- TIMER_MARGIN_MSEC. EXPECT_TRUE(delta < TIMER_MARGIN_MSEC); } TEST_F(IntervalTimerTest, destructIntervalTimer) { // Note: This test currently takes 6 seconds. The timer should have // finer granularity and timer periods in this test should be shorter // in the future. // This code isn't exception safe, but we'd rather keep the code // simpler and more readable as this is only for tests and if it throws // the program would immediately terminate anyway. // The call back function will not be called after the timer is // destructed. // // There are two timers: // itimer_counter (A) // (Calls TimerCallBackCounter) // - increments internal counter in callback function // itimer_canceller (B) // (Calls TimerCallBackCancelDeleter) // - first time of callback, it stores the counter value of // callback_canceller and destructs itimer_counter // - second time of callback, it compares the counter value of // callback_canceller with stored value // if they are same the timer was not called; expected result // if they are different the timer was called after destructed // // 0 1 2 3 4 5 6 (s) // (A) i-----+--x // ^ // |destruct itimer_counter // (B) i--------+--------s // ^stop io_service // and test itimer_counter have been stopped // // itimer_counter will be deleted in TimerCallBackCancelDeleter IntervalTimer* itimer_counter = new IntervalTimer(io_service_); IntervalTimer itimer_canceller(io_service_); timer_cancel_success_ = false; TimerCallBackCounter callback_canceller(this); itimer_counter->setupTimer(callback_canceller, 2); itimer_canceller.setupTimer( TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller), 3); io_service_.run(); EXPECT_TRUE(timer_cancel_success_); } TEST_F(IntervalTimerTest, overwriteIntervalTimer) { // Note: This test currently takes 4 seconds. The timer should have // finer granularity and timer periods in this test should be shorter // in the future. // Calling setupTimer() multiple times updates call back function // and interval. // // There are two timers: // itimer (A) // (Calls TimerCallBackCounter / TimerCallBack) // - increments internal counter in callback function // (TimerCallBackCounter) // interval: 2 seconds // - io_service_.stop() (TimerCallBack) // interval: 1 second // itimer_overwriter (B) // (Calls TimerCallBackOverwriter) // - first time of callback, it calls setupTimer() to change // call back function and interval of itimer to // TimerCallBack / 1 second // after 3 + 1 seconds from the beginning of this test, // TimerCallBack() will be called and io_service_ stops. // - second time of callback, it means the test fails. // // 0 1 2 3 4 5 6 (s) // (A) i-----+--C--s // ^ ^stop io_service // |change call back function // (B) i--------+--------S // ^(stop io_service on fail) // IntervalTimer itimer(io_service_); IntervalTimer itimer_overwriter(io_service_); // store start time boost::posix_time::ptime start; start = boost::posix_time::microsec_clock::universal_time(); itimer.setupTimer(TimerCallBackCounter(this), 2); itimer_overwriter.setupTimer(TimerCallBackOverwriter(this, itimer), 3); io_service_.run(); // reaches here after timer expired // if interval is updated, it takes // 3 seconds for TimerCallBackOverwriter // + 1 second for TimerCallBack (stop) // = 4 seconds. // otherwise (test fails), it takes // 3 seconds for TimerCallBackOverwriter // + 3 seconds for TimerCallBackOverwriter (stop) // = 6 seconds. // delta: difference between elapsed time and 3 + 1 seconds boost::posix_time::time_duration delta = (boost::posix_time::microsec_clock::universal_time() - start) - boost::posix_time::seconds(3 + 1); if (delta.is_negative()) { delta.invert_sign(); } // expect callback function is updated: TimerCallBack is called EXPECT_TRUE(timer_called_); // expect interval is updated EXPECT_TRUE(delta < TIMER_MARGIN_MSEC); } }