// 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 #include #include "../asiolink.h" #include "../address_entry.h" #include "../nameserver_entry.h" #include "../nameserver_address.h" #include "../zone_entry.h" #include "nsas_test.h" using namespace isc::nsas; using namespace asiolink; using namespace std; using namespace isc::dns; using namespace rdata; using namespace boost; namespace { /// \brief Test Fixture Class class NameserverEntryTest : public TestWithRdata { protected: /// \short Just a really stupid callback counting times called struct Callback : public NameserverEntry::Callback { size_t count; virtual void operator()(shared_ptr) { count ++; } Callback() : count(0) { } }; private: /** * \short Fills an rrset into the NameserverEntry trough resolver. * * This function is used when we want to pass data to a NameserverEntry * trough the resolver. * \param resolver The resolver used by the NameserverEntry * \param index Index of the query in the resolver. * \param set The answer. If the pointer is empty, it is taken * as a failure. */ void fillSet(shared_ptr resolver, size_t index, shared_ptr set) { if (set) { resolver->requests[index].second->success(set); } else { resolver->requests[index].second->failure(); } } protected: /// Fills the nameserver entry with data trough ask IP void fillNSEntry(shared_ptr entry, shared_ptr rrv4, shared_ptr rrv6) { // Prepare data to run askIP shared_ptr resolver(new TestResolver); shared_ptr callback(new Callback); // Let it ask for data entry->askIP(resolver, callback, ANY_OK); // Check it really asked and sort the queries EXPECT_TRUE(resolver->asksIPs(Name(entry->getName()), 0, 1)); // Respond with answers fillSet(resolver, 0, rrv4); fillSet(resolver, 1, rrv6); } }; /// Tests of the default constructor TEST_F(NameserverEntryTest, DefaultConstructor) { // Default constructor should not create any RRsets NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN()); EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName()); EXPECT_EQ(RRClass::IN(), alpha.getClass()); // Also check that no addresses have been created. NameserverEntry::AddressVector addresses; alpha.getAddresses(addresses); EXPECT_TRUE(addresses.empty()); } // Test the the RTT on tthe created addresses is not 0 and is different TEST_F(NameserverEntryTest, InitialRTT) { // Get the RTT for the different addresses shared_ptr alpha(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(alpha, rrv4_, rrv6_); NameserverEntry::AddressVector vec; alpha->getAddresses(vec); // Check they are not 0 and they are all small, they should be some kind // of randomish numbers, so we can't expect much more here BOOST_FOREACH(NameserverAddress& entry, vec) { EXPECT_GT(entry.getAddressEntry().getRTT(), 0); // 20 is some arbitrary small value EXPECT_LT(entry.getAddressEntry().getRTT(), 20); } } // Set an address RTT to a given value TEST_F(NameserverEntryTest, SetRTT) { // Get the RTT for the different addresses shared_ptr alpha(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(alpha, rrv4_, rrv6_); NameserverEntry::AddressVector vec; alpha->getAddresses(vec); ASSERT_TRUE(vec.size() > 0); // Take the first address and change the RTT. IOAddress first_address = vec[0].getAddress(); uint32_t first_rtt = vec[0].getAddressEntry().getRTT(); uint32_t new_rtt = first_rtt + 42; alpha->setAddressRTT(first_address, new_rtt); // Now see if it has changed NameserverEntry::AddressVector newvec; alpha->getAddresses(newvec); int matchcount = 0; for (NameserverEntry::AddressVectorIterator i = newvec.begin(); i != newvec.end(); ++i) { if (i->getAddress().equal(first_address)) { ++matchcount; EXPECT_EQ(i->getAddressEntry().getRTT(), new_rtt); } } // ... and make sure there was only one match in the set we retrieved EXPECT_EQ(1, matchcount); } // Set an address RTT to be unreachable TEST_F(NameserverEntryTest, Unreachable) { // Get the RTT for the different addresses shared_ptr alpha(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(alpha, rrv4_, rrv6_); NameserverEntry::AddressVector vec; alpha->getAddresses(vec); ASSERT_TRUE(vec.size() > 0); // Take the first address and mark as unreachable. IOAddress first_address = vec[0].getAddress(); EXPECT_FALSE(vec[0].getAddressEntry().isUnreachable()); alpha->setAddressUnreachable(first_address); // Now see if it has changed NameserverEntry::AddressVector newvec; alpha->getAddresses(newvec); int matchcount = 0; for (NameserverEntry::AddressVectorIterator i = newvec.begin(); i != newvec.end(); ++i) { if (i->getAddress().equal(first_address)) { ++matchcount; EXPECT_TRUE(i->getAddressEntry().isUnreachable()); } } // ... and make sure there was only one match in the set we retrieved EXPECT_EQ(1, matchcount); } // Test that the expiration time of records is set correctly. // // Note that for testing purposes we use the three-argument NameserverEntry // constructor (where we supply the time). It eliminates intermittent errors // cause when this test is run just as the clock "ticks over" to another second. // TODO Return the way where we can pass time inside somehow TEST_F(NameserverEntryTest, ExpirationTime) { time_t curtime = time(NULL); time_t expiration = 0; // Test where there is a single TTL shared_ptr alpha(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(alpha, rrv4_, shared_ptr()); expiration = alpha->getExpiration(); EXPECT_EQ(expiration, curtime + rrv4_->getTTL().getValue()); shared_ptr beta(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(beta, shared_ptr(), rrv6_); expiration = beta->getExpiration(); EXPECT_EQ(expiration, curtime + rrv6_->getTTL().getValue()); // Test where there are two different TTLs EXPECT_NE(rrv4_->getTTL().getValue(), rrv6_->getTTL().getValue()); shared_ptr gamma(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(gamma, rrv4_, rrv6_); uint32_t minttl = min(rrv4_->getTTL().getValue(), rrv6_->getTTL().getValue()); expiration = gamma->getExpiration(); EXPECT_EQ(expiration, curtime + minttl); // Finally check where we don't specify a current time. All we know is // that the expiration time should be greater than the TTL (as the current // time is greater than zero). shared_ptr delta(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(delta, rrv4_, shared_ptr()); EXPECT_GT(delta->getExpiration(), rrv4_->getTTL().getValue()); } // Test that the name of this nameserver is set correctly. TEST_F(NameserverEntryTest, CheckName) { // Default constructor NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN()); EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName()); } // Check that it can cope with non-IN classes. TEST_F(NameserverEntryTest, CheckClass) { // Default constructor NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::CH()); EXPECT_EQ(RRClass::CH(), alpha.getClass()); } // Tests if it asks the IP addresses and calls callbacks when it comes // including the right addresses are returned and that they expire TEST_F(NameserverEntryTest, IPCallbacks) { shared_ptr entry(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); shared_ptr callback(new Callback); shared_ptr resolver(new TestResolver); entry->askIP(resolver, callback, ANY_OK); // Ensure it becomes IN_PROGRESS EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState()); // Now, there should be two queries in the resolver EXPECT_EQ(2, resolver->requests.size()); ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1)); // Another one might ask entry->askIP(resolver, callback, V4_ONLY); // There should still be only two queries in the resolver ASSERT_EQ(2, resolver->requests.size()); // Another one, with need of IPv6 address entry->askIP(resolver, callback, V6_ONLY); // Answer one and see that the callbacks are called resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(), rdata::in::A("192.0.2.1")); // Both callbacks that want IPv4 should be called by now EXPECT_EQ(2, callback->count); // It should contain one IP address NameserverEntry::AddressVector addresses; // Still in progress, waiting for the other address EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getAddresses(addresses)); EXPECT_EQ(1, addresses.size()); // Answer IPv6 address // It is with zero TTL, so it should expire right away resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(), rdata::in::AAAA("2001:db8::1"), 0); // The other callback should appear EXPECT_EQ(3, callback->count); // It should return the one address. It should be expired, but // we ignore it for now EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true)); // Another address should appear EXPECT_EQ(2, addresses.size()); // But when we do not ignore it, it should not appear EXPECT_EQ(Fetchable::EXPIRED, entry->getAddresses(addresses, V6_ONLY)); EXPECT_EQ(2, addresses.size()); } // Test the callback is called even when the address is unreachable TEST_F(NameserverEntryTest, IPCallbacksUnreachable) { shared_ptr entry(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); shared_ptr callback(new Callback); shared_ptr resolver(new TestResolver); // Ask for its IP entry->askIP(resolver, callback, ANY_OK); // Check it asks the resolver EXPECT_EQ(2, resolver->requests.size()); ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1)); resolver->requests[0].second->failure(); // It should still wait for the second one EXPECT_EQ(0, callback->count); EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState()); // It should call the callback now and be unrechable resolver->requests[1].second->failure(); EXPECT_EQ(1, callback->count); EXPECT_EQ(Fetchable::UNREACHABLE, entry->getState()); NameserverEntry::AddressVector addresses; EXPECT_EQ(Fetchable::UNREACHABLE, entry->getAddresses(addresses)); EXPECT_EQ(0, addresses.size()); } /* * Tests that it works even when we provide the answer right away, directly * from resolve. */ TEST_F(NameserverEntryTest, DirectAnswer) { shared_ptr entry(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); shared_ptr callback(new Callback); shared_ptr resolver(new TestResolver); resolver->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::A()), rrv4_); resolver->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::AAAA()), rrv6_); resolver->addPresetAnswer(Question(Name(EXAMPLE_NET), RRClass::IN(), RRType::A()), shared_ptr()); resolver->addPresetAnswer(Question(Name(EXAMPLE_NET), RRClass::IN(), RRType::AAAA()), shared_ptr()); // A successfull test first entry->askIP(resolver, callback, ANY_OK); EXPECT_EQ(0, resolver->requests.size()); EXPECT_EQ(1, callback->count); NameserverEntry::AddressVector addresses; EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses)); EXPECT_EQ(5, addresses.size()); // An unsuccessfull test callback->count = 0; entry.reset(new NameserverEntry(EXAMPLE_NET, RRClass::IN())); entry->askIP(resolver, callback, ANY_OK); EXPECT_EQ(0, resolver->requests.size()); EXPECT_EQ(1, callback->count); addresses.clear(); EXPECT_EQ(Fetchable::UNREACHABLE, entry->getAddresses(addresses)); EXPECT_EQ(0, addresses.size()); } /* * This one tests if it works when the data time out and a different * data is received the next time. */ TEST_F(NameserverEntryTest, ChangedExpired) { shared_ptr entry(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); shared_ptr callback(new Callback); shared_ptr resolver(new TestResolver); // Ask the first time entry->askIP(resolver, callback, V4_ONLY); entry->askIP(resolver, callback, V6_ONLY); EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1)); EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState()); resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(), rdata::in::A("192.0.2.1"), 0); resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(), rdata::in::AAAA("2001:db8::1"), 0); EXPECT_EQ(2, callback->count); NameserverEntry::AddressVector addresses; // We must accept expired as well, as the TTL is 0 (and it is OK, // because we just got the callback) EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY, true)); ASSERT_EQ(1, addresses.size()); EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText()); EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true)); ASSERT_EQ(2, addresses.size()); EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText()); // Ask the second time. The callbacks should not fire right away and it // should request the addresses again entry->askIP(resolver, callback, V4_ONLY); entry->askIP(resolver, callback, V6_ONLY); EXPECT_EQ(2, callback->count); EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 2, 3)); EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState()); resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(), rdata::in::A("192.0.2.2")); resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(), rdata::in::AAAA("2001:db8::2")); // We should get the new addresses and they should not expire, // so we should get them without accepting expired EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY)); ASSERT_EQ(3, addresses.size()); EXPECT_EQ("192.0.2.2", addresses[2].getAddress().toText()); EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY)); ASSERT_EQ(4, addresses.size()); EXPECT_EQ("2001:db8::2", addresses[3].getAddress().toText()); } /* * When the data expire and is asked again, the original RTT is kept. */ TEST_F(NameserverEntryTest, KeepRTT) { shared_ptr entry(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); shared_ptr callback(new Callback); shared_ptr resolver(new TestResolver); // Ask the first time entry->askIP(resolver, callback, V4_ONLY); entry->askIP(resolver, callback, V6_ONLY); EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1)); EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState()); resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(), rdata::in::A("192.0.2.1"), 0); resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(), rdata::in::AAAA("2001:db8::1"), 0); EXPECT_EQ(2, callback->count); NameserverEntry::AddressVector addresses; // We must accept expired as well, as the TTL is 0 (and it is OK, // because we just got the callback) EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY, true)); ASSERT_EQ(1, addresses.size()); EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText()); EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true)); ASSERT_EQ(2, addresses.size()); EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText()); BOOST_FOREACH(const NameserverAddress& address, addresses) { entry->setAddressRTT(address.getAddress(), 123); } // Ask the second time. The callbacks should not fire right away and it // should request the addresses again entry->askIP(resolver, callback, V4_ONLY); entry->askIP(resolver, callback, V6_ONLY); EXPECT_EQ(2, callback->count); EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 2, 3)); EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState()); resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(), rdata::in::A("192.0.2.1")); resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(), rdata::in::AAAA("2001:db8::1")); // We should get the new addresses and they should not expire, // so we should get them without accepting expired addresses.clear(); EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY)); ASSERT_EQ(1, addresses.size()); EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText()); EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY)); ASSERT_EQ(2, addresses.size()); EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText()); // They should have the RTT we set to them BOOST_FOREACH(NameserverAddress& address, addresses) { EXPECT_EQ(123, address.getAddressEntry().getRTT()); } } // Test the RTT is updated smoothly TEST_F(NameserverEntryTest, UpdateRTT) { shared_ptr ns(new NameserverEntry(EXAMPLE_CO_UK, RRClass::IN())); fillNSEntry(ns, rrv4_, rrv6_); NameserverEntry::AddressVector vec; ns->getAddresses(vec); // Initialize the rtt with a small value uint32_t init_rtt = 1; ns->setAddressRTT(vec[0].getAddress(), init_rtt); // The rtt will be stablized to a large value uint32_t stable_rtt = 100; // Update the rtt vec[0].updateRTT(stable_rtt); vec.clear(); ns->getAddresses(vec); uint32_t new_rtt = vec[0].getAddressEntry().getRTT(); // The rtt should not close to new rtt immediately EXPECT_TRUE((stable_rtt - new_rtt) > (new_rtt - init_rtt)); // Update the rtt for enough times for(int i = 0; i < 10000; ++i){ vec[0].updateRTT(stable_rtt); } vec.clear(); ns->getAddresses(vec); new_rtt = vec[0].getAddressEntry().getRTT(); // The rtt should be close to stable rtt value EXPECT_TRUE((stable_rtt - new_rtt) < (new_rtt - init_rtt)); } } // namespace