|
@@ -0,0 +1,753 @@
|
|
|
+// 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 <gtest/gtest.h>
|
|
|
+#include <boost/shared_ptr.hpp>
|
|
|
+#include <boost/foreach.hpp>
|
|
|
+#include <boost/lexical_cast.hpp>
|
|
|
+#include <cmath>
|
|
|
+
|
|
|
+#include <dns/rrclass.h>
|
|
|
+#include <dns/rdataclass.h>
|
|
|
+
|
|
|
+#include "../asiolink.h"
|
|
|
+#include "../zone_entry.h"
|
|
|
+#include "../nameserver_entry.h"
|
|
|
+#include "../address_request_callback.h"
|
|
|
+#include "../nsas_entry_compare.h"
|
|
|
+#include "../hash_deleter.h"
|
|
|
+
|
|
|
+#include "nsas_test.h"
|
|
|
+
|
|
|
+using namespace isc::nsas;
|
|
|
+using namespace asiolink;
|
|
|
+using namespace std;
|
|
|
+using namespace boost;
|
|
|
+using namespace isc::dns;
|
|
|
+
|
|
|
+namespace {
|
|
|
+
|
|
|
+/// \brief Inherited version with access into its internals for tests
|
|
|
+class InheritedZoneEntry : public ZoneEntry {
|
|
|
+ public:
|
|
|
+ InheritedZoneEntry(shared_ptr<ResolverInterface> resolver,
|
|
|
+ const std::string& name, const RRClass& class_code,
|
|
|
+ shared_ptr<HashTable<NameserverEntry> > nameserver_table,
|
|
|
+ shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
|
|
|
+ ZoneEntry(resolver, name, class_code, nameserver_table,
|
|
|
+ nameserver_lru)
|
|
|
+ { }
|
|
|
+ NameserverVector& nameservers() { return nameservers_; }
|
|
|
+};
|
|
|
+
|
|
|
+/// \brief Test Fixture Class
|
|
|
+class ZoneEntryTest : public TestWithRdata {
|
|
|
+protected:
|
|
|
+ /// \brief Constructor
|
|
|
+ ZoneEntryTest() :
|
|
|
+ nameserver_table_(new HashTable<NameserverEntry>(
|
|
|
+ new NsasEntryCompare<NameserverEntry>)),
|
|
|
+ nameserver_lru_(new LruList<NameserverEntry>(
|
|
|
+ (3 * nameserver_table_->tableSize()),
|
|
|
+ new HashDeleter<NameserverEntry>(*nameserver_table_))),
|
|
|
+ resolver_(new TestResolver),
|
|
|
+ callback_(new Callback)
|
|
|
+ { }
|
|
|
+ /// \brief Tables of nameservers to pass into zone entry constructor
|
|
|
+ shared_ptr<HashTable<NameserverEntry> > nameserver_table_;
|
|
|
+ shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
|
|
|
+ /// \brief The resolver
|
|
|
+ shared_ptr<TestResolver> resolver_;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * \short A callback we use into the zone.
|
|
|
+ *
|
|
|
+ * It counts failures and stores successufll results.
|
|
|
+ */
|
|
|
+ struct Callback : public AddressRequestCallback {
|
|
|
+ Callback() : unreachable_count_(0) {}
|
|
|
+ size_t unreachable_count_;
|
|
|
+ vector<NameserverAddress> successes_;
|
|
|
+ virtual void unreachable() { unreachable_count_ ++; }
|
|
|
+ virtual void success(const NameserverAddress& address) {
|
|
|
+ successes_.push_back(address);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ shared_ptr<Callback> callback_;
|
|
|
+
|
|
|
+ // Empty callback to pass to nameserver entry, to do injection of them
|
|
|
+ struct NseCallback : public NameserverEntry::Callback {
|
|
|
+ virtual void operator()(shared_ptr<NameserverEntry>) { }
|
|
|
+ };
|
|
|
+
|
|
|
+ shared_ptr<NseCallback> nseCallback() {
|
|
|
+ return (shared_ptr<NseCallback>(new NseCallback));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * \short Function returning a new zone.
|
|
|
+ *
|
|
|
+ * Convenience funcion, just creating a new zone, to shorten the code.
|
|
|
+ */
|
|
|
+ shared_ptr<InheritedZoneEntry> getZone() {
|
|
|
+ return (shared_ptr<InheritedZoneEntry>(new InheritedZoneEntry(
|
|
|
+ resolver_, EXAMPLE_CO_UK, RRClass::IN(), nameserver_table_,
|
|
|
+ nameserver_lru_)));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * \short Creates and injects a NameserverEntry
|
|
|
+ *
|
|
|
+ * This is used by few tests checking it works when the nameserver
|
|
|
+ * hash table already contains the NameserverEntry. This function
|
|
|
+ * creates one and puts it into the hash table.
|
|
|
+ */
|
|
|
+ shared_ptr<NameserverEntry> injectEntry() {
|
|
|
+ shared_ptr<NameserverEntry> nse(new NameserverEntry(ns_name_.toText(),
|
|
|
+ RRClass::IN()));
|
|
|
+ nameserver_table_->add(nse, HashKey(ns_name_.toText(), RRClass::IN()));
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, nse->getState());
|
|
|
+ return (nse);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * \short Common part of few tests.
|
|
|
+ *
|
|
|
+ * All the tests NameserverEntryReady, NameserverEntryNotAsked,
|
|
|
+ * NameserverEntryInProgress, NameserverEntryExpired,
|
|
|
+ * NameserverEntryUnreachable check that it does not break
|
|
|
+ * when the nameserver hash table already contains the nameserver
|
|
|
+ * in one of the Fetchable::State.
|
|
|
+ *
|
|
|
+ * All the tests prepare the NameserverEntry and then call this
|
|
|
+ * to see if the zone really works. This asks and checks if it
|
|
|
+ * asks for the IP addresses or not and if it succeeds or fails.
|
|
|
+ *
|
|
|
+ * \param answer Should it ask for IP addresses of the nameserver?
|
|
|
+ * If not, it expects it already asked during the preparation
|
|
|
+ * (therefore the request count in resolver is 2).
|
|
|
+ * \param success_count How many callbacks to the zone should
|
|
|
+ * succeed.
|
|
|
+ * \param failure_count How many callbacks to the zone should
|
|
|
+ * fail.
|
|
|
+ */
|
|
|
+ void checkInjected(bool answer, size_t success_count = 1,
|
|
|
+ size_t failure_count = 0)
|
|
|
+ {
|
|
|
+ // Create the zone and check it acts correctly
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
|
|
|
+ RRType::NS()), rr_single_);
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(2, resolver_->requests.size());
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
|
|
|
+ if (answer) {
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1")));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::1")));
|
|
|
+ }
|
|
|
+ // Check the answers
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ EXPECT_EQ(failure_count, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(success_count, callback_->successes_.size());
|
|
|
+ for (size_t i = 0; i < callback_->successes_.size(); ++ i) {
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[i].getAddress()) ||
|
|
|
+ IOAddress("2001:db8::1").equal(
|
|
|
+ callback_->successes_[i].getAddress()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/// Tests of the default constructor
|
|
|
+TEST_F(ZoneEntryTest, DefaultConstructor) {
|
|
|
+
|
|
|
+ // Default constructor should not create any RRsets
|
|
|
+ InheritedZoneEntry alpha(resolver_, EXAMPLE_CO_UK,
|
|
|
+ RRClass::IN(), nameserver_table_, nameserver_lru_);
|
|
|
+ EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
|
|
|
+ EXPECT_EQ(RRClass::IN(), alpha.getClass());
|
|
|
+ EXPECT_TRUE(alpha.nameservers().empty());
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Test with no nameservers.
|
|
|
+ *
|
|
|
+ * When we create a zone that does not have any nameservers,
|
|
|
+ * it should return failures right away (eg. no queries to nameservers
|
|
|
+ * should be generated anywhere and the failure should be provided).
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, CallbackNoNS) {
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ // feed it with a callback
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ // Give it the (empty) nameserver list
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_empty_));
|
|
|
+ // It should tell us it is unreachable.
|
|
|
+ EXPECT_TRUE(callback_->successes_.empty());
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Test how the zone behaves when the list of nameserves change.
|
|
|
+ *
|
|
|
+ * We use TTL of 0, so it asks every time for new list of nameservers.
|
|
|
+ * This allows us to pass a different list each time.
|
|
|
+ *
|
|
|
+ * So, this implicitly tests that it behaves correctly with 0 TTL as well,
|
|
|
+ * it means that it answers even with 0 TTL and that it answers only
|
|
|
+ * one query (or, all queries queued at that time).
|
|
|
+ *
|
|
|
+ * We change the list twice, to see it can ask for another nameserver and
|
|
|
+ * then to see if it can return to the previous (already cached) nameserver.
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, ChangedNS) {
|
|
|
+ // Make it zero TTL, so it expires right away
|
|
|
+ rr_single_->setTTL(RRTTL(0));
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ // Feed it with callback
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
|
|
|
+ // It should not be answered yet, it should ask for the IP addresses
|
|
|
+ // (trough the NameserverEntry there)
|
|
|
+ EXPECT_TRUE(callback_->successes_.empty());
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1")));
|
|
|
+ ASSERT_EQ(1, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[0].getAddress()));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::1")));
|
|
|
+ EXPECT_EQ(1, callback_->successes_.size());
|
|
|
+ // It should request the NSs again, as TTL is 0
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(4, resolver_->requests.size());
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+
|
|
|
+ Name different_name("ns.example.com");
|
|
|
+ // Create a different answer
|
|
|
+ RRsetPtr different_ns(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
|
|
|
+ RRType::NS(), RRTTL(0)));
|
|
|
+ different_ns->addRdata(rdata::generic::NS(different_name));
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(3, different_ns));
|
|
|
+ // It should become ready and ask for the new nameserver addresses
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // Answer one of the IP addresses, we should get an address now
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(different_name, 4, 5));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(4, different_name, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.2")));
|
|
|
+ ASSERT_EQ(2, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.2").equal(
|
|
|
+ callback_->successes_[1].getAddress()));
|
|
|
+
|
|
|
+ // And now, switch back, as it timed out again
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(7, resolver_->requests.size());
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ // When we answer with the original, it should still be cached and
|
|
|
+ // we should get the answer
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
|
|
|
+ EXPECT_EQ(7, resolver_->requests.size());
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ ASSERT_EQ(3, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[0].getAddress()));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Check it works when everything is answered.
|
|
|
+ *
|
|
|
+ * This test emulates a situation when all queries for NS rrsets and
|
|
|
+ * IP addresses (of the NameserverEntry objects inside) are answered
|
|
|
+ * positively. All the callbacks should be called and answer to them
|
|
|
+ * provided.
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, CallbacksAnswered) {
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ // Feed it with a callback
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
|
|
|
+ // It should not be answered yet, its NameserverEntry should ask for the
|
|
|
+ // IP addresses
|
|
|
+ EXPECT_TRUE(callback_->successes_.empty());
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
|
|
|
+ // We should be READY, as it marks we have nameservers
|
|
|
+ // (not that they are ready)
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // Give two more callbacks, with different address families
|
|
|
+ zone->addCallback(callback_, V4_ONLY);
|
|
|
+ zone->addCallback(callback_, V6_ONLY);
|
|
|
+ // Nothing more is asked
|
|
|
+ EXPECT_EQ(3, resolver_->requests.size());
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1")));
|
|
|
+ // Two are answered (ANY and V4)
|
|
|
+ ASSERT_EQ(2, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[0].getAddress()));
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[1].getAddress()));
|
|
|
+ // None are rejected
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ // Answer the IPv6 one as well
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::1")));
|
|
|
+ // This should answer the third callback
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ ASSERT_EQ(3, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("2001:db8::1").equal(
|
|
|
+ callback_->successes_[2].getAddress()));
|
|
|
+ // It should think it is ready
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // When we ask something more, it should be answered right away
|
|
|
+ zone->addCallback(callback_, V4_ONLY);
|
|
|
+ EXPECT_EQ(3, resolver_->requests.size());
|
|
|
+ ASSERT_EQ(4, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[3].getAddress()));
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Test zone reachable only on IPv4.
|
|
|
+ *
|
|
|
+ * This test simulates a zone with its nameservers reachable only
|
|
|
+ * over IPv4. It means we answer the NS query, the A query, but
|
|
|
+ * we generate a failure for AAAA.
|
|
|
+ *
|
|
|
+ * The callbacks asking for any address and IPv4 address should be
|
|
|
+ * called successfully, while the ones asking for IPv6 addresses should
|
|
|
+ * fail.
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, CallbacksAOnly) {
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
|
|
|
+ // It should not be answered yet, it should ask for the IP addresses
|
|
|
+ EXPECT_TRUE(callback_->successes_.empty());
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // Give two more callbacks, with different address families
|
|
|
+ zone->addCallback(callback_, V4_ONLY);
|
|
|
+ zone->addCallback(callback_, V6_ONLY);
|
|
|
+ ASSERT_GE(resolver_->requests.size(), 3);
|
|
|
+ // We tell its NameserverEntry we can't get IPv6 address
|
|
|
+ resolver_->requests[2].second->failure();
|
|
|
+ // One should be rejected (V6_ONLY one), but two still stay
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ // Answer the A one and see it answers what can be answered
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1")));
|
|
|
+ ASSERT_EQ(2, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[0].getAddress()));
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[1].getAddress()));
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ // Everything arriwed, so we are ready
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // Try asking something more and see it asks no more
|
|
|
+ zone->addCallback(callback_, V4_ONLY);
|
|
|
+ EXPECT_EQ(3, resolver_->requests.size());
|
|
|
+ ASSERT_EQ(3, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[2].getAddress()));
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+
|
|
|
+ zone->addCallback(callback_, V6_ONLY);
|
|
|
+ EXPECT_EQ(3, resolver_->requests.size());
|
|
|
+ EXPECT_EQ(3, callback_->successes_.size());
|
|
|
+ EXPECT_EQ(2, callback_->unreachable_count_);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Test with unreachable and v6-reachable nameserver.
|
|
|
+ *
|
|
|
+ * In this test we have a zone with two nameservers. The first one of them
|
|
|
+ * is unreachable, it will not have any addresses. We check that the ZoneEntry
|
|
|
+ * is patient and asks and waits for the second one and then returns the
|
|
|
+ * (successful) answers to us.
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, CallbackTwoNS) {
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ zone->addCallback(callback_, V4_ONLY);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rrns_));
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // It asks a question (we do not know which nameserver)
|
|
|
+ shared_ptr<Name> name;
|
|
|
+ ASSERT_NO_THROW(name.reset(new Name((*resolver_)[1]->getName())));
|
|
|
+ ASSERT_TRUE(resolver_->asksIPs(*name, 1, 2));
|
|
|
+ resolver_->requests[1].second->failure();
|
|
|
+ // Nothing should be answered or failed yet, there's second one
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+ ASSERT_TRUE(resolver_->asksIPs((*resolver_)[3]->getName(), 3, 4));
|
|
|
+ // Fail the second one
|
|
|
+ resolver_->requests[3].second->failure();
|
|
|
+ // The callback should be failed now, as there is no chance of getting
|
|
|
+ // v4 address
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+ // And question for v6 or any should still wait while v4 should be failed
|
|
|
+ // right away
|
|
|
+ zone->addCallback(callback_, V6_ONLY);
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+
|
|
|
+ zone->addCallback(callback_, V4_ONLY);
|
|
|
+ EXPECT_EQ(2, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+ // Answer the IPv6 one
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(2, (*resolver_)[2]->getName(),
|
|
|
+ RRType::AAAA(), rdata::in::AAAA("2001:db8::1")));
|
|
|
+
|
|
|
+ // Ready, as we have at last some address
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // The other callbacks should be answered now
|
|
|
+ EXPECT_EQ(2, callback_->unreachable_count_);
|
|
|
+ ASSERT_EQ(2, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("2001:db8::1").equal(
|
|
|
+ callback_->successes_[0].getAddress()));
|
|
|
+ EXPECT_TRUE(IOAddress("2001:db8::1").equal(
|
|
|
+ callback_->successes_[1].getAddress()));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short This test checks it works with answers from cache.
|
|
|
+ *
|
|
|
+ * The resolver might provide the answer by calling the callback both sometime
|
|
|
+ * later or directly from its resolve method, causing recursion back inside
|
|
|
+ * the ZoneEntry. This test checks it works even in the second case (eg. that
|
|
|
+ * the ZoneEntry is able to handle callback called directly from the
|
|
|
+ * resolve). Tries checking both positive and negative answers.
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, DirectAnswer) {
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+
|
|
|
+ // One unsuccessfull attempt, nameservers fail
|
|
|
+ resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
|
|
|
+ RRType::NS()), shared_ptr<AbstractRRset>());
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, resolver_->requests.size());
|
|
|
+ EXPECT_EQ(Fetchable::UNREACHABLE, zone->getState());
|
|
|
+
|
|
|
+ // Successfull attempt now
|
|
|
+ zone = getZone();
|
|
|
+ // First, fill the answers to all the questions it should ask
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
|
|
|
+ RRType::NS()), rr_single_);
|
|
|
+ Name ns_name("ns.example.net");
|
|
|
+ rrv4_->setName(ns_name);
|
|
|
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(), RRType::A()),
|
|
|
+ rrv4_);
|
|
|
+ rrv6_->setName(ns_name);
|
|
|
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(),
|
|
|
+ RRType::AAAA()), rrv6_);
|
|
|
+ // Reset the results
|
|
|
+ callback_->unreachable_count_ = 0;
|
|
|
+ // Ask for the IP address
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ // It should be answered right away, positively
|
|
|
+ EXPECT_EQ(1, callback_->successes_.size());
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, resolver_->requests.size());
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+
|
|
|
+ // Reset the results
|
|
|
+ callback_->successes_.clear();
|
|
|
+ // Now, pretend we do not have IP addresses
|
|
|
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(), RRType::A()),
|
|
|
+ shared_ptr<AbstractRRset>());
|
|
|
+ resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(),
|
|
|
+ RRType::AAAA()), shared_ptr<AbstractRRset>());
|
|
|
+ // Get another zone and ask it again. It should fail.
|
|
|
+ // Clean the table first, though, so it does not find the old nameserver
|
|
|
+ nameserver_table_->remove(HashKey(ns_name.toText(), RRClass::IN()));
|
|
|
+ zone = getZone();
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(0, callback_->successes_.size());
|
|
|
+ EXPECT_EQ(1, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(0, resolver_->requests.size());
|
|
|
+ // It should be ready, but have no IP addresses on the nameservers
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Test it works with timeouting NameserverEntries.
|
|
|
+ *
|
|
|
+ * In this test we have a zone with nameserver addresses at TTL 0.
|
|
|
+ * So, the NameserverEntry expires each time the ZoneEntry tries to get
|
|
|
+ * its addresses and must ask it again.
|
|
|
+ */
|
|
|
+TEST_F(ZoneEntryTest, AddressTimeout) {
|
|
|
+ shared_ptr<InheritedZoneEntry> zone(getZone());
|
|
|
+ EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
|
|
|
+ // It should not be answered yet, it should ask for the IP addresses
|
|
|
+ EXPECT_TRUE(callback_->successes_.empty());
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
|
|
|
+ // We should be READY, as it marks we have nameservers
|
|
|
+ // (not that they are ready)
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1"), 0));
|
|
|
+ // It answers, not rejects
|
|
|
+ ASSERT_EQ(1, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[0].getAddress()));
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ // As well with IPv6
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::1"), 0));
|
|
|
+ EXPECT_EQ(1, callback_->successes_.size());
|
|
|
+ EXPECT_EQ(Fetchable::READY, zone->getState());
|
|
|
+ // When we ask for another one, it should ask for the addresses again
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 3, 4));
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ EXPECT_EQ(1, callback_->successes_.size());
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(3, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1"), 0));
|
|
|
+ EXPECT_EQ(0, callback_->unreachable_count_);
|
|
|
+ ASSERT_EQ(2, callback_->successes_.size());
|
|
|
+ EXPECT_TRUE(IOAddress("192.0.2.1").equal(
|
|
|
+ callback_->successes_[1].getAddress()));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \short Injection tests.
|
|
|
+ *
|
|
|
+ * These tests check the ZoneEntry does not break when the nameserver hash
|
|
|
+ * table already contains a NameserverEntry in some given state. Each test
|
|
|
+ * for a different state.
|
|
|
+ */
|
|
|
+//@{
|
|
|
+
|
|
|
+/// \short Test how the zone reacts to a nameserver entry in ready state
|
|
|
+TEST_F(ZoneEntryTest, NameserverEntryReady) {
|
|
|
+ // Inject the entry
|
|
|
+ shared_ptr<NameserverEntry> nse(injectEntry());
|
|
|
+ // Fill it with data
|
|
|
+ nse->askIP(resolver_, nseCallback(), ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1")));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::1")));
|
|
|
+ EXPECT_EQ(Fetchable::READY, nse->getState());
|
|
|
+
|
|
|
+ checkInjected(false);
|
|
|
+}
|
|
|
+
|
|
|
+/// \short Test how the zone reacts to a nameserver in not asked state
|
|
|
+TEST_F(ZoneEntryTest, NameserverEntryNotAsked) {
|
|
|
+ // Inject the entry
|
|
|
+ injectEntry();
|
|
|
+ // We do not need it, nothing to modify on it
|
|
|
+
|
|
|
+ checkInjected(true);
|
|
|
+}
|
|
|
+
|
|
|
+/// \short What if the zone finds a nameserver in progress?
|
|
|
+TEST_F(ZoneEntryTest, NameserverEntryInProgress) {
|
|
|
+ // Prepare the nameserver entry
|
|
|
+ shared_ptr<NameserverEntry> nse(injectEntry());
|
|
|
+ nse->askIP(resolver_, nseCallback(), ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
|
|
|
+
|
|
|
+ checkInjected(true);
|
|
|
+}
|
|
|
+
|
|
|
+/// \short Check Zone's reaction to found expired nameserver
|
|
|
+TEST_F(ZoneEntryTest, NameserverEntryExpired) {
|
|
|
+ shared_ptr<NameserverEntry> nse(injectEntry());
|
|
|
+ nse->askIP(resolver_, nseCallback(), ANY_OK);
|
|
|
+ EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
|
|
|
+ rdata::in::A("192.0.2.1"), 0));
|
|
|
+ EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::1"), 0));
|
|
|
+ EXPECT_EQ(Fetchable::READY, nse->getState());
|
|
|
+ NameserverEntry::AddressVector addresses;
|
|
|
+ EXPECT_EQ(Fetchable::EXPIRED, nse->getAddresses(addresses));
|
|
|
+ EXPECT_EQ(Fetchable::EXPIRED, nse->getState());
|
|
|
+ resolver_->requests.clear();
|
|
|
+
|
|
|
+ checkInjected(true);
|
|
|
+}
|
|
|
+
|
|
|
+/// \short Check how it reacts to an unreachable zone already in the table
|
|
|
+TEST_F(ZoneEntryTest, NameserverEntryUnreachable) {
|
|
|
+ shared_ptr<NameserverEntry> nse(injectEntry());
|
|
|
+ nse->askIP(resolver_, nseCallback(), ANY_OK);
|
|
|
+ ASSERT_EQ(2, resolver_->requests.size());
|
|
|
+ resolver_->requests[0].second->failure();
|
|
|
+ resolver_->requests[1].second->failure();
|
|
|
+ EXPECT_EQ(Fetchable::UNREACHABLE, nse->getState());
|
|
|
+
|
|
|
+ checkInjected(false, 0, 1);
|
|
|
+}
|
|
|
+
|
|
|
+//@}
|
|
|
+
|
|
|
+// Count hits of each address
|
|
|
+void
|
|
|
+countHits(size_t *counts, const vector<NameserverAddress>& successes) {
|
|
|
+ BOOST_FOREACH(const NameserverAddress& address, successes) {
|
|
|
+ // We use the last digit as an index
|
|
|
+ string address_string(address.getAddress().toText());
|
|
|
+ size_t index(address_string[address_string.size() - 1] - '0' - 1);
|
|
|
+ ASSERT_LT(index, 3);
|
|
|
+ counts[index] ++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Select one address from the address list
|
|
|
+TEST_F(ZoneEntryTest, AddressSelection) {
|
|
|
+ const size_t repeats = 100000;
|
|
|
+ // Create the zone, give it 2 nameservers and total of 3 addresses
|
|
|
+ // (one of them is ipv6)
|
|
|
+ shared_ptr<ZoneEntry> zone(getZone());
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ EXPECT_NO_THROW(resolver_->provideNS(0, rrns_));
|
|
|
+ ASSERT_GT(resolver_->requests.size(), 1);
|
|
|
+ Name name1(resolver_->requests[1].first->getName());
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(name1, 1, 2));
|
|
|
+ resolver_->answer(1, name1, RRType::A(), rdata::in::A("192.0.2.1"));
|
|
|
+ resolver_->answer(2, name1, RRType::AAAA(),
|
|
|
+ rdata::in::AAAA("2001:db8::2"));
|
|
|
+ ASSERT_GT(resolver_->requests.size(), 3);
|
|
|
+ Name name2(resolver_->requests[3].first->getName());
|
|
|
+ EXPECT_TRUE(resolver_->asksIPs(name2, 3, 4));
|
|
|
+ resolver_->answer(3, name2, RRType::A(), rdata::in::A("192.0.2.3"));
|
|
|
+ resolver_->requests[4].second->failure();
|
|
|
+
|
|
|
+ shared_ptr<NameserverEntry> ns1(nameserver_table_->get(HashKey(
|
|
|
+ name1.toText(), RRClass::IN()))),
|
|
|
+ ns2(nameserver_table_->get(HashKey(name2.toText(), RRClass::IN())));
|
|
|
+
|
|
|
+ size_t counts[3] = {0, 0, 0};
|
|
|
+ callback_->successes_.clear();
|
|
|
+
|
|
|
+ // Test they have the same probabilities when they have the same RTT
|
|
|
+ for (size_t i(0); i < repeats; ++ i) {
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ }
|
|
|
+ countHits(counts, callback_->successes_);
|
|
|
+ // We repeat the simulation for N=repeats times
|
|
|
+ // for each address, the probability is p = 1/3, the average mu = N*p
|
|
|
+ // variance sigma^2 = N * p * (1-p) = N * 1/3 * 2/3 = N*2/9
|
|
|
+ // sigma = sqrt(N*2/9)
|
|
|
+ // we should make sure that mu - 4sigma < c < mu + 4sigma
|
|
|
+ // which means for 99.99366% of the time this should be true
|
|
|
+ double p = 1.0 / 3.0;
|
|
|
+ double mu = repeats * p;
|
|
|
+ double sigma = sqrt(repeats * p * (1 - p));
|
|
|
+ for (size_t i(0); i < 3; ++ i) {
|
|
|
+ ASSERT_TRUE(fabs(counts[i] - mu) < 4*sigma);
|
|
|
+ }
|
|
|
+
|
|
|
+ // reset the environment
|
|
|
+ callback_->successes_.clear();
|
|
|
+ counts[0] = counts[1] = counts[2] = 0;
|
|
|
+
|
|
|
+ // Test when the RTT is not the same
|
|
|
+ ns1->setAddressRTT(IOAddress("192.0.2.1"), 1);
|
|
|
+ ns1->setAddressRTT(IOAddress("2001:db8::2"), 2);
|
|
|
+ ns2->setAddressRTT(IOAddress("192.0.2.3"), 3);
|
|
|
+ for (size_t i(0); i < repeats; ++ i) {
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ }
|
|
|
+ countHits(counts, callback_->successes_);
|
|
|
+ // We expect that the selection probability for each address that
|
|
|
+ // it will be in the range of [mu-4Sigma, mu+4Sigma]
|
|
|
+ double ps[3];
|
|
|
+ ps[0] = 1.0/(1.0 + 1.0/4.0 + 1.0/9.0);
|
|
|
+ ps[1] = (1.0/4.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
|
|
|
+ ps[2] = (1.0/9.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
|
|
|
+ for (size_t i(0); i < 3; ++ i) {
|
|
|
+ double mu = repeats * ps[i];
|
|
|
+ double sigma = sqrt(repeats * ps[i] * (1 - ps[i]));
|
|
|
+ ASSERT_TRUE(fabs(counts[i] - mu < 4 * sigma));
|
|
|
+ }
|
|
|
+
|
|
|
+ // reset the environment
|
|
|
+ callback_->successes_.clear();
|
|
|
+ counts[0] = counts[1] = counts[2] = 0;
|
|
|
+
|
|
|
+ // Test with unreachable address
|
|
|
+ ns1->setAddressRTT(IOAddress("192.0.2.1"), 1);
|
|
|
+ ns1->setAddressRTT(IOAddress("2001:db8::2"), 100);
|
|
|
+ ns2->setAddressUnreachable(IOAddress("192.0.2.3"));
|
|
|
+ for (size_t i(0); i < repeats; ++ i) {
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ }
|
|
|
+ countHits(counts, callback_->successes_);
|
|
|
+ // The unreachable one shouldn't be called
|
|
|
+ EXPECT_EQ(0, counts[2]);
|
|
|
+
|
|
|
+ // reset the environment
|
|
|
+ callback_->successes_.clear();
|
|
|
+ counts[0] = counts[1] = counts[2] = 0;
|
|
|
+
|
|
|
+ // Test with all unreachable
|
|
|
+ ns1->setAddressUnreachable(IOAddress("192.0.2.1"));
|
|
|
+ ns1->setAddressUnreachable(IOAddress("2001:db8::2"));
|
|
|
+ ns2->setAddressUnreachable(IOAddress("192.0.2.3"));
|
|
|
+ for (size_t i(0); i < repeats; ++ i) {
|
|
|
+ zone->addCallback(callback_, ANY_OK);
|
|
|
+ }
|
|
|
+ countHits(counts, callback_->successes_);
|
|
|
+ // They should have about the same probability
|
|
|
+ for (size_t i(0); i < 3; ++ i) {
|
|
|
+ ASSERT_TRUE(fabs(counts[i] - mu) < 4*sigma);
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: The unreachable server should be changed to reachable after 5minutes, but how to test?
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace
|