mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 14:05:33 +00:00
Merge branch 'master' of ssh://bind10.isc.org/var/bind10/git/bind10
This commit is contained in:
@@ -38,7 +38,6 @@ template<class T> class LruList;
|
|||||||
|
|
||||||
namespace nsas {
|
namespace nsas {
|
||||||
|
|
||||||
class ResolverInterface;
|
|
||||||
template<class T> class HashTable;
|
template<class T> class HashTable;
|
||||||
class ZoneEntry;
|
class ZoneEntry;
|
||||||
class NameserverEntry;
|
class NameserverEntry;
|
||||||
|
@@ -51,7 +51,8 @@ public:
|
|||||||
ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
|
ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
|
||||||
ns_->askIP(resolver_.get(), boost::shared_ptr<Callback>(new Callback), ANY_OK);
|
ns_->askIP(resolver_.get(), boost::shared_ptr<Callback>(new Callback), ANY_OK);
|
||||||
resolver_->asksIPs(name_, 0, 1);
|
resolver_->asksIPs(name_, 0, 1);
|
||||||
resolver_->requests[0].second->success(createResponseMessage(rrv4_));
|
resolver_->requests[0].second->success(
|
||||||
|
isc::util::unittests::createResponseMessage(rrv4_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the sample NameserverEntry
|
// Return the sample NameserverEntry
|
||||||
|
@@ -72,7 +72,8 @@ private:
|
|||||||
RRsetPtr set)
|
RRsetPtr set)
|
||||||
{
|
{
|
||||||
if (set) {
|
if (set) {
|
||||||
resolver->requests[index].second->success(createResponseMessage(set));
|
resolver->requests[index].second->success(
|
||||||
|
isc::util::unittests::createResponseMessage(set));
|
||||||
} else {
|
} else {
|
||||||
resolver->requests[index].second->failure();
|
resolver->requests[index].second->failure();
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
#include <util/buffer.h>
|
#include <util/buffer.h>
|
||||||
|
#include <util/unittests/resolver.h>
|
||||||
#include <dns/message.h>
|
#include <dns/message.h>
|
||||||
#include <dns/rdata.h>
|
#include <dns/rdata.h>
|
||||||
#include <dns/rrtype.h>
|
#include <dns/rrtype.h>
|
||||||
@@ -35,24 +36,12 @@
|
|||||||
#include <dns/rcode.h>
|
#include <dns/rcode.h>
|
||||||
#include <dns/messagerenderer.h>
|
#include <dns/messagerenderer.h>
|
||||||
#include <dns/rdataclass.h>
|
#include <dns/rdataclass.h>
|
||||||
#include <resolve/resolver_interface.h>
|
|
||||||
#include "../nsas_entry.h"
|
#include "../nsas_entry.h"
|
||||||
|
|
||||||
using namespace isc::dns::rdata;
|
using namespace isc::dns::rdata;
|
||||||
using namespace isc::dns;
|
using namespace isc::dns;
|
||||||
using namespace isc::util;
|
using namespace isc::util;
|
||||||
|
using isc::util::unittests::TestResolver;
|
||||||
namespace {
|
|
||||||
MessagePtr
|
|
||||||
createResponseMessage(RRsetPtr answer_rrset)
|
|
||||||
{
|
|
||||||
MessagePtr response(new Message(Message::RENDER));
|
|
||||||
response->setOpcode(Opcode::QUERY());
|
|
||||||
response->setRcode(Rcode::NOERROR());
|
|
||||||
response->addRRset(Message::SECTION_ANSWER, answer_rrset);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace isc {
|
namespace isc {
|
||||||
namespace dns {
|
namespace dns {
|
||||||
@@ -223,139 +212,6 @@ private:
|
|||||||
|
|
||||||
static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
|
static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This pretends to be a resolver. It stores the queries and
|
|
||||||
* they can be answered.
|
|
||||||
*/
|
|
||||||
class TestResolver : public isc::resolve::ResolverInterface {
|
|
||||||
private:
|
|
||||||
bool checkIndex(size_t index) {
|
|
||||||
return (requests.size() > index);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef std::map<isc::dns::Question, RRsetPtr >
|
|
||||||
PresetAnswers;
|
|
||||||
PresetAnswers answers_;
|
|
||||||
public:
|
|
||||||
typedef pair<QuestionPtr, CallbackPtr> Request;
|
|
||||||
vector<Request> requests;
|
|
||||||
|
|
||||||
/// \brief Destructor
|
|
||||||
///
|
|
||||||
/// This is important. All callbacks in the requests vector must be
|
|
||||||
/// called to remove them from internal loops. Without this, destroying
|
|
||||||
/// the NSAS object will leave memory assigned.
|
|
||||||
~TestResolver() {
|
|
||||||
for (size_t i = 0; i < requests.size(); ++i) {
|
|
||||||
requests[i].second->failure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void resolve(const QuestionPtr& q, const CallbackPtr& c) {
|
|
||||||
PresetAnswers::iterator it(answers_.find(*q));
|
|
||||||
if (it == answers_.end()) {
|
|
||||||
requests.push_back(Request(q, c));
|
|
||||||
} else {
|
|
||||||
if (it->second) {
|
|
||||||
c->success(createResponseMessage(it->second));
|
|
||||||
} else {
|
|
||||||
c->failure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add a preset answer. If shared_ptr() is passed (eg. NULL),
|
|
||||||
* it will generate failure. If the question is not preset,
|
|
||||||
* it goes to requests and you can answer later.
|
|
||||||
*/
|
|
||||||
void addPresetAnswer(const isc::dns::Question& question,
|
|
||||||
RRsetPtr answer)
|
|
||||||
{
|
|
||||||
answers_[question] = answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thrown if the query at the given index does not exist.
|
|
||||||
class NoSuchRequest : public std::exception { };
|
|
||||||
|
|
||||||
// Thrown if the answer does not match the query
|
|
||||||
class DifferentRequest : public std::exception { };
|
|
||||||
|
|
||||||
QuestionPtr operator[](size_t index) {
|
|
||||||
if (index >= requests.size()) {
|
|
||||||
throw NoSuchRequest();
|
|
||||||
}
|
|
||||||
return (requests[index].first);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Looks if the two provided requests in resolver are A and AAAA.
|
|
||||||
* Sorts them so index1 is A.
|
|
||||||
*
|
|
||||||
* Returns false if there aren't enough elements
|
|
||||||
*/
|
|
||||||
bool asksIPs(const Name& name, size_t index1, size_t index2) {
|
|
||||||
size_t max = (index1 < index2) ? index2 : index1;
|
|
||||||
if (!checkIndex(max)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
EXPECT_EQ(name, (*this)[index1]->getName());
|
|
||||||
EXPECT_EQ(name, (*this)[index2]->getName());
|
|
||||||
EXPECT_EQ(RRClass::IN(), (*this)[index1]->getClass());
|
|
||||||
EXPECT_EQ(RRClass::IN(), (*this)[index2]->getClass());
|
|
||||||
// If they are the other way around, swap
|
|
||||||
if ((*this)[index1]->getType() == RRType::AAAA() &&
|
|
||||||
(*this)[index2]->getType() == RRType::A())
|
|
||||||
{
|
|
||||||
TestResolver::Request tmp((*this).requests[index1]);
|
|
||||||
(*this).requests[index1] =
|
|
||||||
(*this).requests[index2];
|
|
||||||
(*this).requests[index2] = tmp;
|
|
||||||
}
|
|
||||||
// Check the correct addresses
|
|
||||||
EXPECT_EQ(RRType::A(), (*this)[index1]->getType());
|
|
||||||
EXPECT_EQ(RRType::AAAA(), (*this)[index2]->getType());
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sends a simple answer to a query.
|
|
||||||
* 1) Provide index of a query and the address(es) to pass.
|
|
||||||
* 2) Provide index of query and components of address to pass.
|
|
||||||
*/
|
|
||||||
void answer(size_t index, RRsetPtr& set) {
|
|
||||||
if (index >= requests.size()) {
|
|
||||||
throw NoSuchRequest();
|
|
||||||
}
|
|
||||||
requests[index].second->success(createResponseMessage(set));
|
|
||||||
}
|
|
||||||
|
|
||||||
void answer(size_t index, const Name& name, const RRType& type,
|
|
||||||
const rdata::Rdata& rdata, size_t TTL = 100)
|
|
||||||
{
|
|
||||||
RRsetPtr set(new RRset(name, RRClass::IN(),
|
|
||||||
type, RRTTL(TTL)));
|
|
||||||
set->addRdata(rdata);
|
|
||||||
answer(index, set);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void provideNS(size_t index,
|
|
||||||
RRsetPtr nameservers)
|
|
||||||
{
|
|
||||||
if (index >= requests.size()) {
|
|
||||||
throw NoSuchRequest();
|
|
||||||
}
|
|
||||||
if (requests[index].first->getName() != nameservers->getName() ||
|
|
||||||
requests[index].first->getType() != RRType::NS())
|
|
||||||
{
|
|
||||||
throw DifferentRequest();
|
|
||||||
}
|
|
||||||
requests[index].second->success(createResponseMessage(nameservers));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// String constants. These should end in a dot.
|
// String constants. These should end in a dot.
|
||||||
static const std::string EXAMPLE_CO_UK("example.co.uk.");
|
static const std::string EXAMPLE_CO_UK("example.co.uk.");
|
||||||
static const std::string EXAMPLE_NET("example.net.");
|
static const std::string EXAMPLE_NET("example.net.");
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
#include <dns/message.h>
|
#include <dns/message.h>
|
||||||
#include <dns/opcode.h>
|
#include <dns/opcode.h>
|
||||||
#include <dns/exceptions.h>
|
#include <dns/exceptions.h>
|
||||||
|
#include <dns/rdataclass.h>
|
||||||
|
|
||||||
#include <resolve/resolve.h>
|
#include <resolve/resolve.h>
|
||||||
#include <cache/resolver_cache.h>
|
#include <cache/resolver_cache.h>
|
||||||
@@ -48,6 +49,65 @@ using namespace isc::asiolink;
|
|||||||
namespace isc {
|
namespace isc {
|
||||||
namespace asiodns {
|
namespace asiodns {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Function to check if the given name/class has any address in the cache
|
||||||
|
bool
|
||||||
|
hasAddress(const Name& name, const RRClass& rrClass,
|
||||||
|
const isc::cache::ResolverCache& cache)
|
||||||
|
{
|
||||||
|
// FIXME: If we are single-stack and we get only the other type of
|
||||||
|
// address, what should we do? In that case, it will be considered
|
||||||
|
// unreachable, which is most probably true, because A and AAAA will
|
||||||
|
// usually have the same RTT, so we should have both or none from the
|
||||||
|
// glue.
|
||||||
|
return (cache.lookup(name, RRType::A(), rrClass) != RRsetPtr() ||
|
||||||
|
cache.lookup(name, RRType::AAAA(), rrClass) != RRsetPtr());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Find deepest usable delegation in the cache
|
||||||
|
///
|
||||||
|
/// This finds the deepest delegation we have in cache and is safe to use.
|
||||||
|
/// It is not public function, therefore it's not in header. But it's not
|
||||||
|
/// in anonymous namespace, so we can call it from unittests.
|
||||||
|
/// \param name The name we want to delegate to.
|
||||||
|
/// \param cache The place too look for known delegations.
|
||||||
|
std::string
|
||||||
|
deepestDelegation(Name name, RRClass rrclass,
|
||||||
|
isc::cache::ResolverCache& cache)
|
||||||
|
{
|
||||||
|
RRsetPtr cachedNS;
|
||||||
|
// Look for delegation point from bottom, until we find one with
|
||||||
|
// IP address or get to root.
|
||||||
|
//
|
||||||
|
// We need delegation with IP address so we can ask it right away.
|
||||||
|
// If we don't have the IP address, we would need to ask above it
|
||||||
|
// anyway in the best case, and the NS could be inside the zone,
|
||||||
|
// and we could get all loopy with the NSAS in the worst case.
|
||||||
|
while (name.getLabelCount() > 1 &&
|
||||||
|
(cachedNS = cache.lookupDeepestNS(name, rrclass)) != RRsetPtr()) {
|
||||||
|
// Look if we have an IP address for the NS
|
||||||
|
for (RdataIteratorPtr ns(cachedNS->getRdataIterator());
|
||||||
|
!ns->isLast(); ns->next()) {
|
||||||
|
// Do we have IP for this specific NS?
|
||||||
|
if (hasAddress(dynamic_cast<const rdata::generic::NS&>(
|
||||||
|
ns->getCurrent()).getNSName(), rrclass,
|
||||||
|
cache)) {
|
||||||
|
// Found one, stop checking and use this zone
|
||||||
|
// (there may be more addresses, that's only better)
|
||||||
|
return (cachedNS->getName().toText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We don't have anything for this one, so try something higher
|
||||||
|
if (name.getLabelCount() > 1) {
|
||||||
|
name = name.split(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback, nothing found, start at root
|
||||||
|
return (".");
|
||||||
|
}
|
||||||
|
|
||||||
typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
|
typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
|
||||||
|
|
||||||
// Here we do not use the typedef above, as the SunStudio compiler
|
// Here we do not use the typedef above, as the SunStudio compiler
|
||||||
@@ -239,7 +299,6 @@ private:
|
|||||||
// if we have a response for our query stored already. if
|
// if we have a response for our query stored already. if
|
||||||
// so, call handlerecursiveresponse(), if not, we call send()
|
// so, call handlerecursiveresponse(), if not, we call send()
|
||||||
void doLookup() {
|
void doLookup() {
|
||||||
cur_zone_ = ".";
|
|
||||||
dlog("doLookup: try cache");
|
dlog("doLookup: try cache");
|
||||||
Message cached_message(Message::RENDER);
|
Message cached_message(Message::RENDER);
|
||||||
isc::resolve::initResponseMessage(question_, cached_message);
|
isc::resolve::initResponseMessage(question_, cached_message);
|
||||||
@@ -255,9 +314,12 @@ private:
|
|||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
dlog("doLookup: get lowest usable delegation from cache");
|
||||||
|
cur_zone_ = deepestDelegation(question_.getName(),
|
||||||
|
question_.getClass(), cache_);
|
||||||
send();
|
send();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the current question to the given nameserver address
|
// Send the current question to the given nameserver address
|
||||||
|
@@ -31,7 +31,9 @@
|
|||||||
#include <dns/rcode.h>
|
#include <dns/rcode.h>
|
||||||
|
|
||||||
#include <util/buffer.h>
|
#include <util/buffer.h>
|
||||||
|
#include <util/unittests/resolver.h>
|
||||||
#include <dns/message.h>
|
#include <dns/message.h>
|
||||||
|
#include <dns/rdataclass.h>
|
||||||
|
|
||||||
#include <nsas/nameserver_address_store.h>
|
#include <nsas/nameserver_address_store.h>
|
||||||
#include <cache/resolver_cache.h>
|
#include <cache/resolver_cache.h>
|
||||||
@@ -59,6 +61,18 @@ using namespace isc::asiolink;
|
|||||||
using namespace isc::dns;
|
using namespace isc::dns;
|
||||||
using namespace isc::util;
|
using namespace isc::util;
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace asiodns {
|
||||||
|
|
||||||
|
// This is defined in recursive_query.cc, but not in header (it's not public
|
||||||
|
// function). So bring it in to be tested.
|
||||||
|
std::string
|
||||||
|
deepestDelegation(Name name, RRClass rrclass,
|
||||||
|
isc::cache::ResolverCache& cache);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const char* const TEST_SERVER_PORT = "53535";
|
const char* const TEST_SERVER_PORT = "53535";
|
||||||
const char* const TEST_CLIENT_PORT = "53536";
|
const char* const TEST_CLIENT_PORT = "53536";
|
||||||
@@ -110,6 +124,9 @@ class RecursiveQueryTest : public ::testing::Test {
|
|||||||
protected:
|
protected:
|
||||||
RecursiveQueryTest();
|
RecursiveQueryTest();
|
||||||
~RecursiveQueryTest() {
|
~RecursiveQueryTest() {
|
||||||
|
// It would delete itself, but after the io_service_, which could
|
||||||
|
// segfailt in case there were unhandled requests
|
||||||
|
resolver_.reset();
|
||||||
if (res_ != NULL) {
|
if (res_ != NULL) {
|
||||||
freeaddrinfo(res_);
|
freeaddrinfo(res_);
|
||||||
}
|
}
|
||||||
@@ -348,12 +365,6 @@ protected:
|
|||||||
private:
|
private:
|
||||||
bool* done_;
|
bool* done_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MockResolver : public isc::resolve::ResolverInterface {
|
|
||||||
void resolve(const QuestionPtr& question,
|
|
||||||
const ResolverInterface::CallbackPtr& callback) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This version of mock server just stops the io_service when it is resumed
|
// This version of mock server just stops the io_service when it is resumed
|
||||||
// the second time. (Used in the clientTimeout test, where resume
|
// the second time. (Used in the clientTimeout test, where resume
|
||||||
@@ -423,16 +434,17 @@ protected:
|
|||||||
vector<uint8_t> callback_data_;
|
vector<uint8_t> callback_data_;
|
||||||
int sock_;
|
int sock_;
|
||||||
struct addrinfo* res_;
|
struct addrinfo* res_;
|
||||||
|
boost::shared_ptr<isc::util::unittests::TestResolver> resolver_;
|
||||||
};
|
};
|
||||||
|
|
||||||
RecursiveQueryTest::RecursiveQueryTest() :
|
RecursiveQueryTest::RecursiveQueryTest() :
|
||||||
dns_service_(NULL), callback_(NULL), callback_protocol_(0),
|
dns_service_(NULL), callback_(NULL), callback_protocol_(0),
|
||||||
callback_native_(-1), sock_(-1), res_(NULL)
|
callback_native_(-1), sock_(-1), res_(NULL),
|
||||||
|
resolver_(new isc::util::unittests::TestResolver())
|
||||||
{
|
{
|
||||||
io_service_ = new IOService();
|
io_service_ = new IOService();
|
||||||
setDNSService(true, true);
|
setDNSService(true, true);
|
||||||
boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
|
nsas_ = new isc::nsas::NameserverAddressStore(resolver_);
|
||||||
nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(RecursiveQueryTest, v6UDPSend) {
|
TEST_F(RecursiveQueryTest, v6UDPSend) {
|
||||||
@@ -857,7 +869,88 @@ TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
|
|||||||
EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
|
EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that we don't start at root when we have a lower NS cached.
|
||||||
|
TEST_F(RecursiveQueryTest, CachedNS) {
|
||||||
|
setDNSService(true, true);
|
||||||
|
|
||||||
|
// Check we have a reasonable fallback - if there's nothing of interest
|
||||||
|
// in the cache, start at root.
|
||||||
|
EXPECT_EQ(".", deepestDelegation(Name("www.somewhere.deep.example.org"),
|
||||||
|
RRClass::IN(), cache_));
|
||||||
|
|
||||||
|
// Prefill the cache. There's a zone with a NS and IP address for one
|
||||||
|
// of them (to see that one is enough) and another deeper one, with NS,
|
||||||
|
// but without IP.
|
||||||
|
RRsetPtr nsUpper(new RRset(Name("example.org"), RRClass::IN(),
|
||||||
|
RRType::NS(), RRTTL(300)));
|
||||||
|
nsUpper->addRdata(rdata::generic::NS(Name("ns.example.org")));
|
||||||
|
nsUpper->addRdata(rdata::generic::NS(Name("ns2.example.org")));
|
||||||
|
|
||||||
|
RRsetPtr nsLower(new RRset(Name("somewhere.deep.example.org"),
|
||||||
|
RRClass::IN(), RRType::NS(), RRTTL(300)));
|
||||||
|
nsLower->addRdata(rdata::generic::NS(Name("ns.somewhere.deep.example.org"))
|
||||||
|
);
|
||||||
|
|
||||||
|
RRsetPtr nsIp(new RRset(Name("ns2.example.org"), RRClass::IN(),
|
||||||
|
RRType::A(), RRTTL(300)));
|
||||||
|
nsIp->addRdata(rdata::in::A("192.0.2.1"));
|
||||||
|
|
||||||
|
// Make sure the test runs in the correct environment (we don't test
|
||||||
|
// the cache, but we need it to unswer this way for the test, so we
|
||||||
|
// just make sure)
|
||||||
|
ASSERT_TRUE(cache_.update(nsUpper));
|
||||||
|
ASSERT_TRUE(cache_.update(nsLower));
|
||||||
|
ASSERT_TRUE(cache_.update(nsIp));
|
||||||
|
RRsetPtr deepest(cache_.lookupDeepestNS(Name(
|
||||||
|
"www.somewhere.deep.example.org"), RRClass::IN()));
|
||||||
|
ASSERT_NE(RRsetPtr(), deepest);
|
||||||
|
ASSERT_EQ(nsLower->getName(), deepest->getName());
|
||||||
|
|
||||||
|
// Direct check of the function that chooses the delegation point
|
||||||
|
// It should not use nsLower, because we don't have IP address for
|
||||||
|
// that one. But it can choose nsUpper.
|
||||||
|
EXPECT_EQ("example.org.",
|
||||||
|
deepestDelegation(Name("www.somewhere.deep.example.org"),
|
||||||
|
RRClass::IN(), cache_));
|
||||||
|
|
||||||
|
// Now more complex and indirect test:
|
||||||
|
// We ask it to resolve the name for us. It will pick up a delegation
|
||||||
|
// point and ask NSAS for it. NSAS will in turn ask resolver for NS record
|
||||||
|
// of the delegation point. We then pick it up from the fake resolver
|
||||||
|
// and check it is the correct one. This checks the delegation point
|
||||||
|
// travels safely trough the whole path there (it would be enough to check
|
||||||
|
// it up to NSAS, but replacing NSAS is more complicated, so we just
|
||||||
|
// include in the test as well for simplicity).
|
||||||
|
|
||||||
|
// Prepare the recursive query
|
||||||
|
vector<pair<string, uint16_t> > roots;
|
||||||
|
roots.push_back(pair<string, uint16_t>("192.0.2.2", 53));
|
||||||
|
|
||||||
|
RecursiveQuery rq(*dns_service_, *nsas_, cache_,
|
||||||
|
vector<pair<string, uint16_t> >(), roots);
|
||||||
|
// Ask a question at the bottom. It should not use the lower NS, because
|
||||||
|
// it would lead to a loop in NS. But it can use the nsUpper one, it has
|
||||||
|
// an IP address and we can avoid asking root.
|
||||||
|
Question q(Name("www.somewhere.deep.example.org"), RRClass::IN(),
|
||||||
|
RRType::A());
|
||||||
|
OutputBufferPtr buffer(new OutputBuffer(0));
|
||||||
|
MessagePtr answer(new Message(Message::RENDER));
|
||||||
|
// The server is here so we have something to pass there
|
||||||
|
MockServer server(*io_service_);
|
||||||
|
rq.resolve(q, answer, buffer, &server);
|
||||||
|
// We don't need to run the service in this test. We are interested only
|
||||||
|
// in the place it starts resolving at
|
||||||
|
|
||||||
|
// Look what is asked by NSAS - it should be our delegation point.
|
||||||
|
EXPECT_NO_THROW(EXPECT_EQ(nsUpper->getName(),
|
||||||
|
(*resolver_)[0]->getName()) <<
|
||||||
|
"It starts resolving at the wrong place") <<
|
||||||
|
"It does not ask NSAS anything, how does it know where to send?";
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: add tests that check whether the cache is updated on succesfull
|
// TODO: add tests that check whether the cache is updated on succesfull
|
||||||
// responses, and not updated on failures.
|
// responses, and not updated on failures.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,6 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
|||||||
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||||
|
|
||||||
lib_LTLIBRARIES = libutil_unittests.la
|
lib_LTLIBRARIES = libutil_unittests.la
|
||||||
libutil_unittests_la_SOURCES = fork.h fork.cc
|
libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
|
||||||
|
|
||||||
CLEANFILES = *.gcno *.gcda
|
CLEANFILES = *.gcno *.gcda
|
||||||
|
193
src/lib/util/unittests/resolver.h
Normal file
193
src/lib/util/unittests/resolver.h
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
// Copyright (C) 2011 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.
|
||||||
|
|
||||||
|
#ifndef UTIL_UNITTEST_RESOLVER_H
|
||||||
|
#define UTIL_UNITTEST_RESOLVER_H
|
||||||
|
|
||||||
|
/// \file resolver.h
|
||||||
|
/// \brief Fake resolver
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <dns/rrset.h>
|
||||||
|
#include <dns/question.h>
|
||||||
|
#include <dns/message.h>
|
||||||
|
#include <dns/opcode.h>
|
||||||
|
#include <dns/rcode.h>
|
||||||
|
#include <dns/rrttl.h>
|
||||||
|
#include <resolve/resolver_interface.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace util {
|
||||||
|
namespace unittests {
|
||||||
|
|
||||||
|
/// \brief Put rrset into a message as an answer
|
||||||
|
inline static isc::dns::MessagePtr
|
||||||
|
createResponseMessage(isc::dns::RRsetPtr answer_rrset)
|
||||||
|
{
|
||||||
|
isc::dns::MessagePtr response(new isc::dns::Message(
|
||||||
|
isc::dns::Message::RENDER));
|
||||||
|
response->setOpcode(isc::dns::Opcode::QUERY());
|
||||||
|
response->setRcode(isc::dns::Rcode::NOERROR());
|
||||||
|
response->addRRset(isc::dns::Message::SECTION_ANSWER, answer_rrset);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Mock resolver
|
||||||
|
///
|
||||||
|
/// This class pretends to be a resolver. However, it only stores the
|
||||||
|
/// requests and can answer them right away by prepared answers. It doesn't
|
||||||
|
/// do any real work and is intended for testing purposes.
|
||||||
|
class TestResolver : public isc::resolve::ResolverInterface {
|
||||||
|
private:
|
||||||
|
bool checkIndex(size_t index) {
|
||||||
|
return (requests.size() > index);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef std::map<isc::dns::Question, isc::dns::RRsetPtr>
|
||||||
|
PresetAnswers;
|
||||||
|
PresetAnswers answers_;
|
||||||
|
public:
|
||||||
|
typedef std::pair<isc::dns::QuestionPtr, CallbackPtr> Request;
|
||||||
|
/// \brief List of requests the tested class sent trough resolve
|
||||||
|
std::vector<Request> requests;
|
||||||
|
|
||||||
|
/// \brief Destructor
|
||||||
|
///
|
||||||
|
/// This is important. All callbacks in the requests vector must be
|
||||||
|
/// called to remove them from internal loops. Without this, destroying
|
||||||
|
/// the NSAS object will leave memory assigned.
|
||||||
|
~TestResolver() {
|
||||||
|
for (size_t i = 0; i < requests.size(); ++i) {
|
||||||
|
requests[i].second->failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Testing version of resolve
|
||||||
|
///
|
||||||
|
/// If there's a prepared answer (provided by addPresetAnswer), it
|
||||||
|
/// answers it right away. Otherwise it just stores the request in
|
||||||
|
/// the requests member so it can be examined later.
|
||||||
|
virtual void resolve(const isc::dns::QuestionPtr& q,
|
||||||
|
const CallbackPtr& c)
|
||||||
|
{
|
||||||
|
PresetAnswers::iterator it(answers_.find(*q));
|
||||||
|
if (it == answers_.end()) {
|
||||||
|
requests.push_back(Request(q, c));
|
||||||
|
} else {
|
||||||
|
if (it->second) {
|
||||||
|
c->success(createResponseMessage(it->second));
|
||||||
|
} else {
|
||||||
|
c->failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Add a preset answer.
|
||||||
|
///
|
||||||
|
/// Add a preset answer. If shared_ptr() is passed (eg. NULL),
|
||||||
|
/// it will generate failure. If the question is not preset,
|
||||||
|
/// it goes to requests and you can answer later.
|
||||||
|
void addPresetAnswer(const isc::dns::Question& question,
|
||||||
|
isc::dns::RRsetPtr answer)
|
||||||
|
{
|
||||||
|
answers_[question] = answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Thrown if the query at the given index does not exist.
|
||||||
|
class NoSuchRequest : public std::exception { };
|
||||||
|
|
||||||
|
/// \brief Thrown if the answer does not match the query
|
||||||
|
class DifferentRequest : public std::exception { };
|
||||||
|
|
||||||
|
/// \brief Provides the question of request on given answer
|
||||||
|
isc::dns::QuestionPtr operator[](size_t index) {
|
||||||
|
if (index >= requests.size()) {
|
||||||
|
throw NoSuchRequest();
|
||||||
|
}
|
||||||
|
return (requests[index].first);
|
||||||
|
}
|
||||||
|
/// \brief Test it asks for IP addresses
|
||||||
|
/// Looks if the two provided requests in resolver are A and AAAA.
|
||||||
|
/// Sorts them so index1 is A.
|
||||||
|
///
|
||||||
|
/// Returns false if there aren't enough elements
|
||||||
|
bool asksIPs(const isc::dns::Name& name, size_t index1,
|
||||||
|
size_t index2)
|
||||||
|
{
|
||||||
|
size_t max = (index1 < index2) ? index2 : index1;
|
||||||
|
if (!checkIndex(max)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(name, (*this)[index1]->getName());
|
||||||
|
EXPECT_EQ(name, (*this)[index2]->getName());
|
||||||
|
EXPECT_EQ(isc::dns::RRClass::IN(), (*this)[index1]->getClass());
|
||||||
|
EXPECT_EQ(isc::dns::RRClass::IN(), (*this)[index2]->getClass());
|
||||||
|
// If they are the other way around, swap
|
||||||
|
if ((*this)[index1]->getType() == isc::dns::RRType::AAAA() &&
|
||||||
|
(*this)[index2]->getType() == isc::dns::RRType::A())
|
||||||
|
{
|
||||||
|
TestResolver::Request tmp((*this).requests[index1]);
|
||||||
|
(*this).requests[index1] =
|
||||||
|
(*this).requests[index2];
|
||||||
|
(*this).requests[index2] = tmp;
|
||||||
|
}
|
||||||
|
// Check the correct addresses
|
||||||
|
EXPECT_EQ(isc::dns::RRType::A(), (*this)[index1]->getType());
|
||||||
|
EXPECT_EQ(isc::dns::RRType::AAAA(), (*this)[index2]->getType());
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Answer a request
|
||||||
|
/// Sends a simple answer to a query.
|
||||||
|
/// 1) Provide index of a query and the address(es) to pass.
|
||||||
|
/// 2) Provide index of query and components of address to pass.
|
||||||
|
void answer(size_t index, isc::dns::RRsetPtr& set) {
|
||||||
|
if (index >= requests.size()) {
|
||||||
|
throw NoSuchRequest();
|
||||||
|
}
|
||||||
|
requests[index].second->success(createResponseMessage(set));
|
||||||
|
}
|
||||||
|
|
||||||
|
void answer(size_t index, const isc::dns::Name& name,
|
||||||
|
const isc::dns::RRType& type,
|
||||||
|
const isc::dns::rdata::Rdata& rdata, size_t TTL = 100)
|
||||||
|
{
|
||||||
|
isc::dns::RRsetPtr set(new isc::dns::RRset(name,
|
||||||
|
isc::dns::RRClass::IN(),
|
||||||
|
type,
|
||||||
|
isc::dns::RRTTL(TTL)));
|
||||||
|
set->addRdata(rdata);
|
||||||
|
answer(index, set);
|
||||||
|
}
|
||||||
|
/// \Answer the query at index by list of nameservers
|
||||||
|
void provideNS(size_t index, isc::dns::RRsetPtr nameservers)
|
||||||
|
{
|
||||||
|
if (index >= requests.size()) {
|
||||||
|
throw NoSuchRequest();
|
||||||
|
}
|
||||||
|
if (requests[index].first->getName() != nameservers->getName() ||
|
||||||
|
requests[index].first->getType() != isc::dns::RRType::NS())
|
||||||
|
{
|
||||||
|
throw DifferentRequest();
|
||||||
|
}
|
||||||
|
requests[index].second->success(createResponseMessage(nameservers));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Reference in New Issue
Block a user