2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 05:27:55 +00:00

Merge branch 'master' of ssh://bind10.isc.org/var/bind10/git/bind10

This commit is contained in:
Jelte Jansen 2011-04-28 10:04:40 +02:00
commit 0310b8b77e
8 changed files with 366 additions and 161 deletions

View File

@ -38,7 +38,6 @@ template<class T> class LruList;
namespace nsas {
class ResolverInterface;
template<class T> class HashTable;
class ZoneEntry;
class NameserverEntry;

View File

@ -51,7 +51,8 @@ public:
ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
ns_->askIP(resolver_.get(), boost::shared_ptr<Callback>(new Callback), ANY_OK);
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

View File

@ -72,7 +72,8 @@ private:
RRsetPtr set)
{
if (set) {
resolver->requests[index].second->success(createResponseMessage(set));
resolver->requests[index].second->success(
isc::util::unittests::createResponseMessage(set));
} else {
resolver->requests[index].second->failure();
}

View File

@ -27,6 +27,7 @@
#include <config.h>
#include <util/buffer.h>
#include <util/unittests/resolver.h>
#include <dns/message.h>
#include <dns/rdata.h>
#include <dns/rrtype.h>
@ -35,24 +36,12 @@
#include <dns/rcode.h>
#include <dns/messagerenderer.h>
#include <dns/rdataclass.h>
#include <resolve/resolver_interface.h>
#include "../nsas_entry.h"
using namespace isc::dns::rdata;
using namespace isc::dns;
using namespace isc::util;
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;
}
}
using isc::util::unittests::TestResolver;
namespace isc {
namespace dns {
@ -223,139 +212,6 @@ private:
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.
static const std::string EXAMPLE_CO_UK("example.co.uk.");
static const std::string EXAMPLE_NET("example.net.");

View File

@ -28,6 +28,7 @@
#include <dns/message.h>
#include <dns/opcode.h>
#include <dns/exceptions.h>
#include <dns/rdataclass.h>
#include <resolve/resolve.h>
#include <cache/resolver_cache.h>
@ -48,6 +49,65 @@ using namespace isc::asiolink;
namespace isc {
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;
// 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
// so, call handlerecursiveresponse(), if not, we call send()
void doLookup() {
cur_zone_ = ".";
dlog("doLookup: try cache");
Message cached_message(Message::RENDER);
isc::resolve::initResponseMessage(question_, cached_message);
@ -255,9 +314,12 @@ private:
stop();
}
} else {
dlog("doLookup: get lowest usable delegation from cache");
cur_zone_ = deepestDelegation(question_.getName(),
question_.getClass(), cache_);
send();
}
}
// Send the current question to the given nameserver address

View File

@ -31,7 +31,9 @@
#include <dns/rcode.h>
#include <util/buffer.h>
#include <util/unittests/resolver.h>
#include <dns/message.h>
#include <dns/rdataclass.h>
#include <nsas/nameserver_address_store.h>
#include <cache/resolver_cache.h>
@ -59,6 +61,18 @@ using namespace isc::asiolink;
using namespace isc::dns;
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 {
const char* const TEST_SERVER_PORT = "53535";
const char* const TEST_CLIENT_PORT = "53536";
@ -110,6 +124,9 @@ class RecursiveQueryTest : public ::testing::Test {
protected:
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) {
freeaddrinfo(res_);
}
@ -348,12 +365,6 @@ protected:
private:
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
// the second time. (Used in the clientTimeout test, where resume
@ -423,16 +434,17 @@ protected:
vector<uint8_t> callback_data_;
int sock_;
struct addrinfo* res_;
boost::shared_ptr<isc::util::unittests::TestResolver> resolver_;
};
RecursiveQueryTest::RecursiveQueryTest() :
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();
setDNSService(true, true);
boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
nsas_ = new isc::nsas::NameserverAddressStore(resolver_);
}
TEST_F(RecursiveQueryTest, v6UDPSend) {
@ -857,7 +869,88 @@ TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
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
// responses, and not updated on failures.
}

View File

@ -2,6 +2,6 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CXXFLAGS = $(B10_CXXFLAGS)
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

View 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