diff --git a/ChangeLog b/ChangeLog index b38df80491..e9ef1cff14 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ + 70. [func] each + Added a hot-spot cache to libdatasrc to speed up access to + repeatedly-queried data and reduce the number of queries to + the underlying database; this should substantially improve + performance. Also added a "-n" ("no cache") option to + bind10 and b10-auth to disable the cache if needed. + (Trac #192, svn r2383) + bind10-devel-20100701 released on July 1, 2010 69. [func]* jelte diff --git a/doc/Doxyfile b/doc/Doxyfile index 02cbf27368..fb029f0e0c 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -568,7 +568,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions +INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc index d9e7a64329..70744b4c08 100644 --- a/src/bin/auth/auth_srv.cc +++ b/src/bin/auth/auth_srv.cc @@ -60,7 +60,7 @@ private: AuthSrvImpl(const AuthSrvImpl& source); AuthSrvImpl& operator=(const AuthSrvImpl& source); public: - AuthSrvImpl(); + AuthSrvImpl(const bool use_cache); isc::data::ElementPtr setDbFile(const isc::data::ElementPtr config); @@ -76,9 +76,13 @@ public: /// Currently non-configurable, but will be. static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096; + + /// Hot spot cache + isc::datasrc::HotCache cache_; }; -AuthSrvImpl::AuthSrvImpl() : cs_(NULL), verbose_mode_(false) +AuthSrvImpl::AuthSrvImpl(const bool use_cache) : + cs_(NULL), verbose_mode_(false) { // cur_datasrc_ is automatically initialized by the default constructor, // effectively being an empty (sqlite) data source. once ccsession is up @@ -86,9 +90,12 @@ AuthSrvImpl::AuthSrvImpl() : cs_(NULL), verbose_mode_(false) // add static data source data_sources_.addDataSrc(ConstDataSrcPtr(new StaticDataSrc)); + + // enable or disable the cache + cache_.setEnabled(use_cache); } -AuthSrv::AuthSrv() : impl_(new AuthSrvImpl) { +AuthSrv::AuthSrv(const bool use_cache) : impl_(new AuthSrvImpl(use_cache)) { } AuthSrv::~AuthSrv() { @@ -239,7 +246,7 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message, message.setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE); try { - Query query(message, dnssec_ok); + Query query(message, impl_->cache_, dnssec_ok); impl_->data_sources_.doQuery(query); } catch (const Exception& ex) { if (impl_->verbose_mode_) { diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h index bb3ea9eb29..8ead4f7fc1 100644 --- a/src/bin/auth/auth_srv.h +++ b/src/bin/auth/auth_srv.h @@ -43,7 +43,7 @@ private: AuthSrv(const AuthSrv& source); AuthSrv& operator=(const AuthSrv& source); public: - explicit AuthSrv(); + explicit AuthSrv(const bool use_cache); ~AuthSrv(); //@} /// \return \c true if the \message contains a response to be returned; diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index bfd97253ee..6f9112b071 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -86,7 +86,7 @@ my_command_handler(const string& command, const ElementPtr args) { void usage() { - cerr << "Usage: b10-auth [-p port] [-4|-6]" << endl; + cerr << "Usage: b10-auth [-p port] [-4|-6] [-nv]" << endl; exit(1); } } // end of anonymous namespace @@ -95,9 +95,9 @@ int main(int argc, char* argv[]) { int ch; const char* port = DNSPORT; - bool use_ipv4 = true, use_ipv6 = true; + bool use_ipv4 = true, use_ipv6 = true, cache = true; - while ((ch = getopt(argc, argv, "46p:v")) != -1) { + while ((ch = getopt(argc, argv, "46np:v")) != -1) { switch (ch) { case '4': // Note that -4 means "ipv4 only", we need to set "use_ipv6" here, @@ -110,6 +110,9 @@ main(int argc, char* argv[]) { // The same note as -4 applies. use_ipv4 = false; break; + case 'n': + cache = false; + break; case 'p': port = optarg; break; @@ -142,7 +145,7 @@ main(int argc, char* argv[]) { specfile = string(AUTH_SPECFILE_LOCATION); } - auth_server = new AuthSrv; + auth_server = new AuthSrv(cache); auth_server->setVerbose(verbose_mode); io_service = new asio_link::IOService(auth_server, port, use_ipv4, diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc index e10bcbf801..d46fb6d812 100644 --- a/src/bin/auth/tests/auth_srv_unittest.cc +++ b/src/bin/auth/tests/auth_srv_unittest.cc @@ -44,7 +44,7 @@ const char* BADCONFIG_TESTDB = class AuthSrvTest : public ::testing::Test { protected: - AuthSrvTest() : request_message(Message::RENDER), + AuthSrvTest() : server(true), request_message(Message::RENDER), parse_message(Message::PARSE), default_qid(0x1035), opcode(Opcode(Opcode::QUERY())), qname("www.example.com"), qclass(RRClass::IN()), qtype(RRType::A()), ibuffer(NULL), diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index 2a4e279b69..29c8170b7a 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -176,8 +176,8 @@ class ProcessInfo: class BoB: """Boss of BIND class.""" - def __init__(self, msgq_socket_file=None, auth_port=5300, verbose=False, - setuid=None, username=None): + def __init__(self, msgq_socket_file=None, auth_port=5300, nocache=False, + verbose=False, setuid=None, username=None): """Initialize the Boss of BIND. This is a singleton (only one can run). @@ -195,6 +195,7 @@ class BoB: self.runnable = False self.uid = setuid self.username = username + self.nocache = nocache def config_handler(self, new_config): if self.verbose: @@ -302,6 +303,8 @@ class BoB: # start b10-auth # XXX: this must be read from the configuration manager in the future authargs = ['b10-auth', '-p', str(self.auth_port)] + if self.nocache: + authargs += ['-n'] if self.verbose: sys.stdout.write("[bind10] Starting b10-auth using port %d\n" % self.auth_port) @@ -557,6 +560,8 @@ def main(): parser = OptionParser(version=__version__) parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="display more about what is going on") + parser.add_option("-n", "--no-cache", action="store_true", dest="nocache", + default=False, help="disable hot-spot cache in b10-auth") parser.add_option("-p", "--port", dest="auth_port", type="string", action="callback", callback=check_port, default="5300", help="port the b10-auth daemon will use (default 5300)") @@ -621,7 +626,7 @@ def main(): # Go bob! boss_of_bind = BoB(options.msgq_socket_file, int(options.auth_port), - options.verbose, setuid, username) + options.nocache, options.verbose, setuid, username) startup_result = boss_of_bind.startup() if startup_result: sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result) diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am index f61849cc91..7c4023a753 100644 --- a/src/lib/datasrc/Makefile.am +++ b/src/lib/datasrc/Makefile.am @@ -13,3 +13,4 @@ libdatasrc_la_SOURCES = data_source.h data_source.cc libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc libdatasrc_la_SOURCES += query.h query.cc +libdatasrc_la_SOURCES += cache.h cache.cc diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc index 2fd30726c2..6e2c654e34 100644 --- a/src/lib/datasrc/data_source.cc +++ b/src/lib/datasrc/data_source.cc @@ -24,6 +24,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -35,9 +39,6 @@ #include -#include "data_source.h" -#include "query.h" - #define RETERR(x) do { \ DataSrc::Result r = (x); \ if (r != DataSrc::SUCCESS) \ @@ -53,7 +54,37 @@ namespace datasrc { typedef boost::shared_ptr ConstNsec3ParamPtr; -namespace { +class ZoneInfo { +public: + ZoneInfo(DataSrc* ts, + const isc::dns::Name& n, + const isc::dns::RRClass& c, + const isc::dns::RRType& t = isc::dns::RRType::ANY()) : + top_source_(ts), + dsm_(((t == RRType::DS() && n.getLabelCount() != 1) + ? n.split(1, n.getLabelCount() - 1) : n), + c) + {} + + const Name* getEnclosingZone() { + if (dsm_.getEnclosingZone() == NULL) { + top_source_->findClosestEnclosure(dsm_); + } + return (dsm_.getEnclosingZone()); + } + + const DataSrc* getDataSource() { + if (dsm_.getDataSource() == NULL) { + top_source_->findClosestEnclosure(dsm_); + } + return (dsm_.getDataSource()); + } + +private: + const DataSrc* top_source_; + DataSrcMatch dsm_; +}; + // Add a task to the query task queue to look up additional data // (i.e., address records for the names included in NS or MX records) void @@ -68,14 +99,14 @@ getAdditional(Query& q, ConstRRsetPtr rrset) { if (rrset->getType() == RRType::NS()) { const generic::NS& ns = dynamic_cast(rd); q.tasks().push(QueryTaskPtr( - new QueryTask(ns.getNSName(), q.qclass(), + new QueryTask(q, ns.getNSName(), Section::ADDITIONAL(), QueryTask::GLUE_QUERY, QueryTask::GETADDITIONAL))); } else if (rrset->getType() == RRType::MX()) { const generic::MX& mx = dynamic_cast(rd); q.tasks().push(QueryTaskPtr( - new QueryTask(mx.getMXName(), q.qclass(), + new QueryTask(q, mx.getMXName(), Section::ADDITIONAL(), QueryTask::NOGLUE_QUERY, QueryTask::GETADDITIONAL))); @@ -125,47 +156,313 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) { return; } + // Stop chasing CNAMES after 16 lookups, to prevent loops if (q.tooMany()) { return; } q.tasks().push(QueryTaskPtr( - new QueryTask(dynamic_cast + new QueryTask(q, dynamic_cast (it->getCurrent()).getCname(), - task->qclass, - task->qtype, - Section::ANSWER(), + task->qtype, Section::ANSWER(), QueryTask::FOLLOWCNAME))); } -// Perform the query specified in a QueryTask object -DataSrc::Result -doQueryTask(const DataSrc* ds, const Name* zonename, QueryTask& task, - RRsetList& target) -{ - switch (task.op) { - case QueryTask::AUTH_QUERY: - return (ds->findRRset(task.qname, task.qclass, task.qtype, - target, task.flags, zonename)); +// Check the cache for data which can answer the current query task. +bool +checkCache(QueryTask& task, RRsetList& target) { + HotCache& cache = task.q.getCache(); + RRsetList rrsets; + RRsetPtr rrset; + int count = 0; + uint32_t flags = 0, cflags = 0; + bool hit = false, found = false; + switch (task.op) { + case QueryTask::SIMPLE_QUERY: // Find exact RRset + // ANY queries must be handled by the low-level data source, + // or the results won't be guaranteed to be complete + if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) { + break; + } + + hit = cache.retrieve(task.qname, task.qclass, task.qtype, rrset, flags); + if (hit) { + if (rrset) { + rrsets.addRRset(rrset); + target.append(rrsets); + } + task.flags = flags; + return (true); + } + break; + + case QueryTask::AUTH_QUERY: // Find exact RRset or CNAME + if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) { + break; + } + + hit = cache.retrieve(task.qname, task.qclass, task.qtype, rrset, flags); + if (!hit || !rrset || (flags & DataSrc::CNAME_FOUND) != 0) { + hit = cache.retrieve(task.qname, task.qclass, RRType::CNAME(), + rrset, flags); + } + + if (hit) { + if (rrset) { + rrsets.addRRset(rrset); + target.append(rrsets); + } + task.flags = flags; + return (true); + } + break; + + case QueryTask::GLUE_QUERY: // Find addresses + case QueryTask::NOGLUE_QUERY: + // (XXX: need to figure out how to deal with noglue case) + flags = 0; + + hit = cache.retrieve(task.qname, task.qclass, RRType::A(), + rrset, cflags); + if (hit) { + flags |= cflags; + ++count; + if (rrset) { + rrsets.addRRset(rrset); + found = true; + } + } + + hit = cache.retrieve(task.qname, task.qclass, RRType::AAAA(), + rrset, flags); + if (hit) { + flags |= cflags; + ++count; + if (rrset) { + rrsets.addRRset(rrset); + found = true; + } + } + + if (count == 2) { + if (found) { + flags &= ~DataSrc::TYPE_NOT_FOUND; + target.append(rrsets); + } + task.flags = flags; + return (true); + } + break; + + + case QueryTask::REF_QUERY: // Find NS, DS and/or DNAME + flags = count = 0; + + hit = cache.retrieve(task.qname, task.qclass, RRType::NS(), + rrset, cflags); + if (hit) { + flags |= cflags; + ++count; + if (rrset) { + rrsets.addRRset(rrset); + found = true; + } + } + + hit = cache.retrieve(task.qname, task.qclass, RRType::DS(), + rrset, flags); + if (hit) { + flags |= cflags; + ++count; + if (rrset) { + rrsets.addRRset(rrset); + found = true; + } + } + + hit = cache.retrieve(task.qname, task.qclass, RRType::DNAME(), + rrset, flags); + if (hit) { + flags |= cflags; + ++count; + if (rrset) { + rrsets.addRRset(rrset); + found = true; + } + } + + if (count == 3) { + if (found) { + flags &= ~DataSrc::TYPE_NOT_FOUND; + flags &= DataSrc::REFERRAL; + target.append(rrsets); + } + task.flags = flags; + return (true); + } + break; + } + + return (false); +} + +// Carry out the query specified in a QueryTask object +DataSrc::Result +doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) { + HotCache& cache = task.q.getCache(); + RRsetPtr rrset; + + // First, check the cache for matching data + if (checkCache(task, target)) { + return (DataSrc::SUCCESS); + } + + // Requested data weren't in the cache (or were, but had expired), + // so now we proceed with the low-level data source lookup, and cache + // whatever we find. + const DataSrc* ds = zoneinfo.getDataSource(); + const Name* const zonename = zoneinfo.getEnclosingZone(); + + if (ds == NULL) { + task.flags |= DataSrc::NO_SUCH_ZONE; + return (DataSrc::SUCCESS); + } + + DataSrc::Result result; + switch (task.op) { case QueryTask::SIMPLE_QUERY: - return (ds->findExactRRset(task.qname, task.qclass, task.qtype, - target, task.flags, zonename)); + result = ds->findExactRRset(task.qname, task.qclass, task.qtype, + target, task.flags, zonename); + + if (result != DataSrc::SUCCESS) { + return (result); + } + + if (task.qclass == RRClass::ANY()) { + // XXX: Currently, RRsetList::findRRset() doesn't handle + // ANY queries, and without that we can't cache the results, + // so we just return in that case. + return (result); + } + + if (task.flags == 0) { + rrset = target.findRRset(task.qtype, task.qclass); + assert(rrset); + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, task.qtype, task.flags); + } + + return (result); + + case QueryTask::AUTH_QUERY: + result = ds->findRRset(task.qname, task.qclass, task.qtype, + target, task.flags, zonename); + + if (result != DataSrc::SUCCESS) { + return (result); + } + + if (task.qclass == RRClass::ANY()) { + return (result); + } + + if (task.qtype == RRType::ANY()) { + BOOST_FOREACH(RRsetPtr rr, target) { + cache.addPositive(rr, task.flags); + } + } else if ((task.flags & DataSrc::CNAME_FOUND) != 0) { + cache.addNegative(task.qname, task.qclass, task.qtype, task.flags); + rrset = target.findRRset(RRType::CNAME(), task.qclass); + assert(rrset); + cache.addPositive(rrset, task.flags); + } else if ((task.flags & DataSrc::DATA_NOT_FOUND) == 0) { + if (task.qtype != RRType::CNAME()) { + cache.addNegative(task.qname, task.qclass, RRType::CNAME(), + task.flags); + } + rrset = target.findRRset(task.qtype, task.qclass); + assert(rrset); + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, task.qtype, task.flags); + } + + return (result); case QueryTask::GLUE_QUERY: case QueryTask::NOGLUE_QUERY: - return (ds->findAddrs(task.qname, task.qclass, target, - task.flags, zonename)); + result = ds->findAddrs(task.qname, task.qclass, target, + task.flags, zonename); + + if (result != DataSrc::SUCCESS) { + return (result); + } + + if (task.qclass == RRClass::ANY()) { + return (result); + } + + rrset = target.findRRset(RRType::A(), task.qclass); + if (rrset) { + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, RRType::A(), task.flags); + } + + rrset = target.findRRset(RRType::AAAA(), task.qclass); + if (rrset) { + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, RRType::AAAA(), + task.flags); + } + + return (result); case QueryTask::REF_QUERY: - return (ds->findReferral(task.qname, task.qclass, target, - task.flags, zonename)); + result = ds->findReferral(task.qname, task.qclass, target, + task.flags, zonename); + + if (result != DataSrc::SUCCESS) { + return (result); + } + + if (task.qclass == RRClass::ANY()) { + return (result); + } + + rrset = target.findRRset(RRType::NS(), task.qclass); + if (rrset) { + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, RRType::NS(), + task.flags); + } + rrset = target.findRRset(RRType::DS(), task.qclass); + if (rrset) { + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, RRType::DS(), + task.flags); + } + rrset = target.findRRset(RRType::DNAME(), task.qclass); + if (rrset) { + cache.addPositive(rrset, task.flags); + } else { + cache.addNegative(task.qname, task.qclass, RRType::DNAME(), + task.flags); + } + + return (result); } // Not reached return (DataSrc::ERROR); } + // Add an RRset (and its associated RRSIG) to a message section, // checking first to ensure that there isn't already an RRset with // the same name and type. @@ -202,12 +499,12 @@ copyAuth(Query& q, RRsetList& auth) { // Query for referrals (i.e., NS/DS or DNAME) at a given name inline bool -refQuery(const Name& name, const RRClass& qclass, const DataSrc* ds, - const Name* zonename, RRsetList& target) +refQuery(const Query& q, const Name& name, ZoneInfo& zoneinfo, + RRsetList& target) { - QueryTask newtask(name, qclass, QueryTask::REF_QUERY); + QueryTask newtask(q, name, QueryTask::REF_QUERY); - if (doQueryTask(ds, zonename, newtask, target) != DataSrc::SUCCESS) { + if (doQueryTask(newtask, zoneinfo, target) != DataSrc::SUCCESS) { // Lookup failed return (false); } @@ -224,16 +521,22 @@ refQuery(const Name& name, const RRClass& qclass, const DataSrc* ds, // referrals. Note that we exclude the apex name and query name themselves; // they'll be handled in a normal lookup in the zone. inline bool -hasDelegation(const DataSrc* ds, const Name* zonename, Query& q, - QueryTaskPtr task) -{ +hasDelegation(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo) { + const Name* const zonename = zoneinfo.getEnclosingZone(); + if (zonename == NULL) { + if (task->state == QueryTask::GETANSWER) { + q.message().setRcode(Rcode::REFUSED()); + } + return (false); + } + const int diff = task->qname.getLabelCount() - zonename->getLabelCount(); if (diff > 1) { bool found = false; RRsetList ref; for (int i = diff - 1; i > 0; --i) { const Name sub(task->qname.split(i)); - if (refQuery(sub, q.qclass(), ds, zonename, ref)) { + if (refQuery(q, sub, zoneinfo, ref)) { found = true; break; } @@ -280,12 +583,12 @@ hasDelegation(const DataSrc* ds, const Name* zonename, Query& q, } inline DataSrc::Result -addSOA(Query& q, const Name* zonename, const DataSrc* ds) { +addSOA(Query& q, ZoneInfo& zoneinfo) { RRsetList soa; - QueryTask newtask(*zonename, q.qclass(), RRType::SOA(), - QueryTask::SIMPLE_QUERY); - RETERR(doQueryTask(ds, zonename, newtask, soa)); + const Name* const zonename = zoneinfo.getEnclosingZone(); + QueryTask newtask(q, *zonename, RRType::SOA(), QueryTask::SIMPLE_QUERY); + RETERR(doQueryTask(newtask, zoneinfo, soa)); if (newtask.flags != 0) { return (DataSrc::ERROR); } @@ -296,14 +599,11 @@ addSOA(Query& q, const Name* zonename, const DataSrc* ds) { } inline DataSrc::Result -addNSEC(Query& q, const QueryTaskPtr task, const Name& name, - const Name& zonename, const DataSrc* ds) -{ +addNSEC(Query& q, const Name& name, ZoneInfo& zoneinfo) { RRsetList nsec; - QueryTask newtask(name, task->qclass, RRType::NSEC(), - QueryTask::SIMPLE_QUERY); - RETERR(doQueryTask(ds, &zonename, newtask, nsec)); + QueryTask newtask(q, name, RRType::NSEC(), QueryTask::SIMPLE_QUERY); + RETERR(doQueryTask(newtask, zoneinfo, nsec)); if (newtask.flags == 0) { addToMessage(q, Section::AUTHORITY(), nsec.findRRset(RRType::NSEC(), q.qclass())); @@ -313,23 +613,31 @@ addNSEC(Query& q, const QueryTaskPtr task, const Name& name, } inline DataSrc::Result -getNsec3(const DataSrc* ds, const Name& zonename, const RRClass& qclass, - string& hash, RRsetPtr& target) -{ +getNsec3(Query& q, ZoneInfo& zoneinfo, string& hash, RRsetPtr& target) { + const DataSrc* ds = zoneinfo.getDataSource(); + const Name* const zonename = zoneinfo.getEnclosingZone(); + + if (ds == NULL) { + q.message().setRcode(Rcode::SERVFAIL()); + return (DataSrc::ERROR); + } + RRsetList rl; - RETERR(ds->findCoveringNSEC3(zonename, hash, rl)); - target = rl.findRRset(RRType::NSEC3(), qclass); + RETERR(ds->findCoveringNSEC3(*zonename, hash, rl)); + target = rl.findRRset(RRType::NSEC3(), q.qclass()); + return (DataSrc::SUCCESS); } ConstNsec3ParamPtr -getNsec3Param(Query& q, const DataSrc* ds, const Name& zonename) { +getNsec3Param(Query& q, ZoneInfo& zoneinfo) { DataSrc::Result result; RRsetList nsec3param; - QueryTask newtask(zonename, q.qclass(), RRType::NSEC3PARAM(), + const Name* const zonename = zoneinfo.getEnclosingZone(); + QueryTask newtask(q, *zonename, RRType::NSEC3PARAM(), QueryTask::SIMPLE_QUERY); - result = doQueryTask(ds, &zonename, newtask, nsec3param); + result = doQueryTask(newtask, zoneinfo, nsec3param); newtask.flags &= ~DataSrc::REFERRAL; if (result != DataSrc::SUCCESS || newtask.flags != 0) { return (ConstNsec3ParamPtr()); @@ -356,15 +664,16 @@ getNsec3Param(Query& q, const DataSrc* ds, const Name& zonename) { } inline DataSrc::Result -proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds, - const Name& zonename, const bool wildcard) -{ - ConstNsec3ParamPtr nsec3 = getNsec3Param(q, ds, zonename); +proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) { + Message& m = q.message(); + const Name* const zonename = zoneinfo.getEnclosingZone(); + ConstNsec3ParamPtr nsec3 = getNsec3Param(q, zoneinfo); + if (nsec3 != NULL) { // Attach the NSEC3 record covering the QNAME RRsetPtr rrset; string hash1(nsec3->getHash(task->qname)); - RETERR(getNsec3(ds, zonename, q.qclass(), hash1, rrset)); + RETERR(getNsec3(q, zoneinfo, hash1, rrset)); addToMessage(q, Section::AUTHORITY(), rrset); // If this is an NXRRSET or NOERROR/NODATA, we're done @@ -373,7 +682,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds, } // Find the closest provable enclosing name for QNAME - Name enclosure(zonename); + Name enclosure(*zonename); const int diff = task->qname.getLabelCount() - enclosure.getLabelCount(); string hash2; @@ -388,7 +697,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds, // hash2 will be overwritten with the actual hash found; // we don't want to use one until we find an exact match - RETERR(getNsec3(ds, zonename, q.qclass(), hash2, rrset)); + RETERR(getNsec3(q, zoneinfo, hash2, rrset)); if (hash2 == nodehash) { addToMessage(q, Section::AUTHORITY(), rrset); break; @@ -403,19 +712,24 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds, // Otherwise, there is no wildcard record, so we must add a // covering NSEC3 to prove that it doesn't exist. string hash3(nsec3->getHash(Name("*").concatenate(enclosure))); - RETERR(getNsec3(ds, zonename, q.qclass(), hash3, rrset)); + RETERR(getNsec3(q, zoneinfo, hash3, rrset)); if (hash3 != hash1 && hash3 != hash2) { addToMessage(q, Section::AUTHORITY(), rrset); } } else { Name nsecname(task->qname); if ((task->flags & DataSrc::NAME_NOT_FOUND) != 0 || wildcard) { - ds->findPreviousName(task->qname, nsecname, &zonename); + const DataSrc* ds = zoneinfo.getDataSource(); + if (ds == NULL) { + m.setRcode(Rcode::SERVFAIL()); + return (DataSrc::ERROR); + } + ds->findPreviousName(task->qname, nsecname, zonename); } - RETERR(addNSEC(q, task, nsecname, zonename, ds)); + RETERR(addNSEC(q, nsecname, zoneinfo)); if ((task->flags & DataSrc::TYPE_NOT_FOUND) != 0 || - nsecname == zonename) + nsecname == *zonename) { return (DataSrc::SUCCESS); } @@ -427,7 +741,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds, // Otherwise, there is no wildcard record, so we must add an // NSEC for the zone to prove the wildcard doesn't exist. - RETERR(addNSEC(q, task, zonename, zonename, ds)); + RETERR(addNSEC(q, *zonename, zoneinfo)); } return (DataSrc::SUCCESS); @@ -435,9 +749,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds, // Attempt a wildcard lookup inline DataSrc::Result -tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, - const Name* zonename, bool& found) -{ +tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) { Message& m = q.message(); DataSrc::Result result; found = false; @@ -448,6 +760,7 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, return (DataSrc::SUCCESS); } + const Name* const zonename = zoneinfo.getEnclosingZone(); const int diff = task->qname.getLabelCount() - zonename->getLabelCount(); if (diff < 1) { return (DataSrc::SUCCESS); @@ -459,9 +772,9 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, for (int i = 1; i <= diff; ++i) { const Name& wname(star.concatenate(task->qname.split(i))); - QueryTask newtask(wname, task->qclass, task->qtype, Section::ANSWER(), + QueryTask newtask(q, wname, task->qtype, Section::ANSWER(), QueryTask::AUTH_QUERY); - result = doQueryTask(ds, zonename, newtask, wild); + result = doQueryTask(newtask, zoneinfo, wild); if (result == DataSrc::SUCCESS) { if (newtask.flags == 0) { task->flags &= ~DataSrc::NAME_NOT_FOUND; @@ -487,7 +800,7 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, if (found) { // Prove the nonexistence of the name we were looking for if (q.wantDnssec()) { - result = proveNX(q, task, ds, *zonename, true); + result = proveNX(q, task, zoneinfo, true); if (result != DataSrc::SUCCESS) { m.setRcode(Rcode::SERVFAIL()); return (DataSrc::ERROR); @@ -511,7 +824,7 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, } RRsetList auth; - if (!refQuery(*zonename, q.qclass(), ds, zonename, auth)) { + if (!refQuery(q, *zonename, zoneinfo, auth)) { return (DataSrc::ERROR); } @@ -521,7 +834,6 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, return (DataSrc::SUCCESS); } -} // end of anonymous namespace // // doQuery: Processes a query. @@ -531,6 +843,12 @@ DataSrc::doQuery(Query& q) { Message& m = q.message(); vector additional; + // Record the fact that the query is being processed by the + // current data source. + q.setDatasrc(this); + + // Process the query task queue. (The queue is initialized + // and the first task placed on it by the Query constructor.) m.clearHeaderFlag(MessageFlag::AA()); while (!q.tasks().empty()) { QueryTaskPtr task = q.tasks().front(); @@ -549,55 +867,29 @@ DataSrc::doQuery(Query& q) { return; } - // Find the closest enclosing zone for which we are authoritative, - // and the concrete data source which is authoritative for it. - // (Note that RRtype DS queries need to go to the parent.) - const int nlabels = task->qname.getLabelCount() - 1; - NameMatch match(nlabels != 0 && task->qtype == RRType::DS() ? - task->qname.split(1) : task->qname); - findClosestEnclosure(match, task->qclass); - const DataSrc* datasource = match.bestDataSrc(); - const Name* zonename = match.closestName(); - - assert((datasource == NULL && zonename == NULL) || - (datasource != NULL && zonename != NULL)); - + ZoneInfo zoneinfo(this, task->qname, task->qclass, task->qtype); RRsetList data; Result result = SUCCESS; - if (datasource) { - // For these query task types, if there is more than - // one level between the zone name and qname, we need to - // check the intermediate nodes for referrals. - if ((task->op == QueryTask::AUTH_QUERY || - task->op == QueryTask::NOGLUE_QUERY) && - hasDelegation(datasource, zonename, q, task)) { - continue; - } + // For these query task types, if there is more than + // one level between the zone name and qname, we need to + // check the intermediate nodes for referrals. + if ((task->op == QueryTask::AUTH_QUERY || + task->op == QueryTask::NOGLUE_QUERY) && + hasDelegation(q, task, zoneinfo)) { + continue; + } - result = doQueryTask(datasource, zonename, *task, data); - if (result != SUCCESS) { - m.setRcode(Rcode::SERVFAIL()); - return; - } + result = doQueryTask(*task, zoneinfo, data); + if (result != SUCCESS) { + m.setRcode(Rcode::SERVFAIL()); + return; + } - // Query found a referral; let's find out if that was expected-- - // i.e., if an NS was at the zone apex, or if we were querying - // specifically for, and found, a DS, NSEC, or DNAME record. - if ((task->flags & REFERRAL) != 0 && - (zonename->getLabelCount() == task->qname.getLabelCount() || - ((task->qtype == RRType::NSEC() || - task->qtype == RRType::DS() || - task->qtype == RRType::DNAME()) && - data.findRRset(task->qtype, task->qclass)))) { - task->flags &= ~REFERRAL; - } - } else { - task->flags = NO_SUCH_ZONE; - - // No such zone. If we're chasing cnames or adding additional - // data, that's okay, but if doing an original query, return - // REFUSED. + // No such zone. If we're chasing cnames or adding additional + // data, that's okay, but if doing an original query, return + // REFUSED. + if (task->flags == NO_SUCH_ZONE) { if (task->state == QueryTask::GETANSWER) { m.setRcode(Rcode::REFUSED()); return; @@ -605,6 +897,19 @@ DataSrc::doQuery(Query& q) { continue; } + // Query found a referral; let's find out if that was expected-- + // i.e., if an NS was at the zone apex, or if we were querying + // specifically for, and found, a DS, NSEC, or DNAME record. + const Name* const zonename = zoneinfo.getEnclosingZone(); + if ((task->flags & REFERRAL) != 0 && + (zonename->getLabelCount() == task->qname.getLabelCount() || + ((task->qtype == RRType::NSEC() || + task->qtype == RRType::DS() || + task->qtype == RRType::DNAME()) && + data.findRRset(task->qtype, task->qclass)))) { + task->flags &= ~REFERRAL; + } + if (result == SUCCESS && task->flags == 0) { bool have_ns = false, need_auth = false; switch (task->state) { @@ -626,13 +931,12 @@ DataSrc::doQuery(Query& q) { // Add the NS records for the enclosing zone to // the authority section. RRsetList auth; - if (!refQuery(*zonename, q.qclass(), datasource, zonename, - auth) || - !auth.findRRset(RRType::NS(), - datasource->getClass())) { + const DataSrc* ds = zoneinfo.getDataSource(); + if (!refQuery(q, Name(*zonename), zoneinfo, auth) || + !auth.findRRset(RRType::NS(), ds->getClass())) { isc_throw(DataSourceError, "NS RR not found in " << *zonename << "/" << - datasource->getClass()); + q.qclass()); } copyAuth(q, auth); @@ -673,8 +977,7 @@ DataSrc::doQuery(Query& q) { if (task->state == QueryTask::GETANSWER) { RRsetList auth; m.clearHeaderFlag(MessageFlag::AA()); - if (!refQuery(task->qname, q.qclass(), datasource, zonename, - auth)) { + if (!refQuery(q, task->qname, zoneinfo, auth)) { m.setRcode(Rcode::SERVFAIL()); return; } @@ -697,7 +1000,7 @@ DataSrc::doQuery(Query& q) { // and the name was not found, we need to find out whether // there are any relevant wildcards. bool wildcard_found = false; - result = tryWildcard(q, task, datasource, zonename, wildcard_found); + result = tryWildcard(q, task, zoneinfo, wildcard_found); if (result != SUCCESS) { m.setRcode(Rcode::SERVFAIL()); return; @@ -719,21 +1022,22 @@ DataSrc::doQuery(Query& q) { m.setRcode(Rcode::NXDOMAIN()); } - result = addSOA(q, zonename, datasource); + result = addSOA(q, zoneinfo); if (result != SUCCESS) { isc_throw(DataSourceError, "SOA RR not found in" << *zonename << - "/" << datasource->getClass()); + "/" << q.qclass()); } } Name nsecname(task->qname); if ((task->flags & NAME_NOT_FOUND) != 0) { - datasource->findPreviousName(task->qname, nsecname, zonename); + const DataSrc* ds = zoneinfo.getDataSource(); + ds->findPreviousName(task->qname, nsecname, zonename); } if (q.wantDnssec()) { - result = proveNX(q, task, datasource, *zonename, false); + result = proveNX(q, task, zoneinfo, false); if (result != DataSrc::SUCCESS) { m.setRcode(Rcode::SERVFAIL()); return; @@ -858,25 +1162,38 @@ MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src) { } void -MetaDataSrc::findClosestEnclosure(NameMatch& match, const RRClass& qclass) const -{ - if (getClass() != qclass && - getClass() != RRClass::ANY() && qclass != RRClass::ANY()) { +MetaDataSrc::findClosestEnclosure(DataSrcMatch& match) const { + if (getClass() != match.getClass() && + getClass() != RRClass::ANY() && match.getClass() != RRClass::ANY()) { return; } BOOST_FOREACH (ConstDataSrcPtr data_src, data_sources) { - data_src->findClosestEnclosure(match, qclass); + data_src->findClosestEnclosure(match); } } -NameMatch::~NameMatch() { +DataSrcMatch::~DataSrcMatch() { delete closest_name_; } void -NameMatch::update(const DataSrc& new_source, const Name& container) { +DataSrcMatch::update(const DataSrc& new_source, const Name& container) { + if (getClass() != new_source.getClass() && getClass() != RRClass::ANY() && + new_source.getClass() != RRClass::ANY()) + { + return; + } + if (closest_name_ == NULL) { + const NameComparisonResult::NameRelation cmp = + getName().compare(container).getRelation(); + if (cmp != NameComparisonResult::EQUAL && + cmp != NameComparisonResult::SUBDOMAIN) + { + return; + } + closest_name_ = new Name(container); best_source_ = &new_source; return; @@ -884,7 +1201,7 @@ NameMatch::update(const DataSrc& new_source, const Name& container) { if (container.compare(*closest_name_).getRelation() == NameComparisonResult::SUBDOMAIN) { - const Name* newname = new Name(container); + Name* newname = new Name(container); delete closest_name_; closest_name_ = newname; best_source_ = &new_source; diff --git a/src/lib/datasrc/data_source.h b/src/lib/datasrc/data_source.h index 13944bad34..88acf3c41c 100644 --- a/src/lib/datasrc/data_source.h +++ b/src/lib/datasrc/data_source.h @@ -40,7 +40,7 @@ class RRsetList; namespace datasrc { -class NameMatch; +class DataSrcMatch; class Query; class DataSrc; @@ -89,12 +89,15 @@ public: // NAME_NOT_FOUND: The node does not exist in the data source. // TYPE_NOT_FOUND: The node does not contain the requested RRType // NO_SUCH_ZONE: The zone does not exist in this data source. + // + // DATA_NOT_FOUND: A combination of the last three, for coding convenience enum QueryResponseFlags { REFERRAL = 0x01, CNAME_FOUND = 0x02, NAME_NOT_FOUND = 0x04, TYPE_NOT_FOUND = 0x08, - NO_SUCH_ZONE = 0x10 + NO_SUCH_ZONE = 0x10, + DATA_NOT_FOUND = (NAME_NOT_FOUND|TYPE_NOT_FOUND|NO_SUCH_ZONE) }; // 'High-level' methods. These will be implemented by the @@ -107,9 +110,7 @@ public: // 'Medium-level' methods. This will be implemented by the general // DataSrc class but MAY be overwritten by subclasses. - virtual void findClosestEnclosure(NameMatch& match, - const isc::dns::RRClass& qclasss) - const = 0; + virtual void findClosestEnclosure(DataSrcMatch& match) const = 0; // Optional 'low-level' methods. These will have stub implementations // in the general DataSrc class but MAY be overwritten by subclasses @@ -179,9 +180,7 @@ public: virtual void doQuery(Query& q); - virtual void findClosestEnclosure(NameMatch& match, - const isc::dns::RRClass& qclass) - const = 0; + virtual void findClosestEnclosure(DataSrcMatch& match) const = 0; const isc::dns::RRClass& getClass() const { return rrclass; } void setClass(isc::dns::RRClass& c) { rrclass = c; } @@ -250,8 +249,7 @@ public: void removeDataSrc(ConstDataSrcPtr data_src); size_t dataSrcCount() { return data_sources.size(); }; - void findClosestEnclosure(NameMatch& match, - const isc::dns::RRClass& qclass) const; + void findClosestEnclosure(DataSrcMatch& match) const; // Actual queries for data should not be sent to a MetaDataSrc object, // so we return NOT_IMPLEMENTED if we receive any. @@ -298,31 +296,107 @@ private: std::vector data_sources; }; -class NameMatch { +/// \brief Information about the zone along with the %data source that best +/// matches a give name and RR class. +/// +/// A \c DataSrcMatch object is created with a domain name and RR class to +/// hold the search state of looking for the zone and the %data source that +/// stores the zone that best match the given name and RR class. +/// The application of this class passes an object of \c DataSrcMatch to +/// one or more ^data sources via their \c findClosestEnclosure() method. +/// The %data source searches its content for the given key, and update +/// the state if it finds a better zone than the currently recorded one. +/// +/// The state of a \c DataSrcMatch object should be updated if and only if: +/// - The specified RR class and the RR class of the %data source are the +// same, or the specified RR class is ANY; and +/// - There is no matching %data source and name found (which is probably +/// wrong, see below), or the given enclosing name gives a longer match +/// than the currently stored enclosing name against the specified name. +class DataSrcMatch { /// /// \name Constructors, Assignment Operator and Destructor. /// - /// Note: The copy constructor and the assignment operator are intentionally - /// defined as private. + /// Note: The copy constructor and the assignment operator are + /// intentionally defined as private. + //@{ private: - NameMatch(const NameMatch& source); - NameMatch& operator=(const NameMatch& source); + DataSrcMatch(const DataSrcMatch& source); + DataSrcMatch& operator=(const DataSrcMatch& source); public: - NameMatch(const isc::dns::Name& qname) : - closest_name_(NULL), best_source_(NULL), qname_(qname) {} - ~NameMatch(); + /// \brief The constructor. + /// + /// This constructor normally doesn't throw an exception. However, + /// it creates a copy of the given name object, which may require memory + /// allocation, and if it fails the corresponding standard exception will + /// be thrown. + /// + /// \param name The domain name to be matched. + /// \param rrclass The RR class to be matched + DataSrcMatch(const isc::dns::Name& name, + const isc::dns::RRClass& rrclass) : + closest_name_(NULL), best_source_(NULL), + name_(name), rrclass_(rrclass) + {} + ~DataSrcMatch(); //@} + /// \name Getter and Setter Methods + //@{ + /// \brief Returns the name to be matched. + const isc::dns::Name& getName() const { return (name_); } + + /// \brief Returns the RR class to be matched. + /// + /// This method never throws an exception. + const isc::dns::RRClass& getClass() const { return (rrclass_); } + + /// \brief Returns the best enclosing zone name found for the given + // name and RR class so far. + /// + /// \return A pointer to the zone apex \c Name, NULL if none found yet. + /// + /// This method never throws an exception. + const isc::dns::Name* getEnclosingZone() const { return (closest_name_); } + + /// \brief Returns the best %data source found for the given name and + /// RR class so far. + /// + /// This method never throws an exception. + /// + /// \return A pointer to a concrete %data source, NULL if none found yet. + const DataSrc* getDataSource() const { return (best_source_); } + //@} + + /// \brief Update the object state with better information if possible. + /// + /// This method is intended to be called by a concrete %data source's + /// \c findClosestEnclosure() method to store the best match for + /// the given name and class that has been found so far. + /// + /// It compares the best name (if found) and \c container, and if the + /// latter gives a longer match, it will install the given %data source + /// and the enclosing name as the best match; + /// if there is no known pair of %data source and enclosing name, + /// this method will install the given pair unconditionally. + /// (which is probably BAD); + /// otherwise this method does nothing. + /// + /// In any case, if a new pair of %data source and enclosing name are + /// installed, a new name object will be internally allocated. + /// And, if memory allocation fails the corresponding standard exception + /// will be thrown. + /// + /// \param new_source A candidate %data source that gives a better match. + /// \param container The enclosing name of the matching zone in + /// \c new_source. void update(const DataSrc& new_source, const isc::dns::Name& container); - const isc::dns::Name& qname() const { return (qname_); } - const isc::dns::Name* closestName() const { return (closest_name_); } - const DataSrc* bestDataSrc() const { return (best_source_); } - private: - const isc::dns::Name* closest_name_; + isc::dns::Name* closest_name_; const DataSrc* best_source_; - const isc::dns::Name qname_; + const isc::dns::Name name_; + const isc::dns::RRClass& rrclass_; }; class Nsec3Param { diff --git a/src/lib/datasrc/query.cc b/src/lib/datasrc/query.cc index 8b3089c0f0..9b431ec392 100644 --- a/src/lib/datasrc/query.cc +++ b/src/lib/datasrc/query.cc @@ -28,36 +28,37 @@ using namespace isc::dns; namespace isc { namespace datasrc { -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect) : - qname(n), qclass(c), qtype(t), section(sect), op(AUTH_QUERY), - state(GETANSWER), flags(0) + q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), + op(AUTH_QUERY), state(GETANSWER), flags(0) {} -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect, const Op o) : - qname(n), qclass(c), qtype(t), section(sect), op(o), state(GETANSWER), - flags(0) + q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), op(o), + state(GETANSWER), flags(0) {} -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect, const State st) : - qname(n), qclass(c), qtype(t), section(sect), op(AUTH_QUERY), state(st), - flags(0) + q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), + op(AUTH_QUERY), state(st), flags(0) {} -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect, const Op o, const State st) : - qname(n), qclass(c), qtype(t), section(sect), op(o), state(st), flags(0) + q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), op(o), + state(st), flags(0) {} -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const isc::dns::RRType& t, const Op o) : - qname(n), qclass(c), qtype(t), section(Section::ANSWER()), op(o), - state(GETANSWER), flags(0) + q(qry), qname(n), qclass(qry.qclass()), qtype(t), + section(Section::ANSWER()), op(o), state(GETANSWER), flags(0) { if (op != SIMPLE_QUERY) { isc_throw(Unexpected, "invalid constructor for this task operation"); @@ -65,21 +66,20 @@ QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, } // A referral query doesn't need to specify section, state, or type. -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, - const Op o) : - qname(n), qclass(c), qtype(RRType::ANY()), section(Section::ANSWER()), - op(o), state(GETANSWER), flags(0) +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const Op o) : + q(qry), qname(n), qclass(qry.qclass()), qtype(RRType::ANY()), + section(Section::ANSWER()), op(o), state(GETANSWER), flags(0) { if (op != REF_QUERY) { isc_throw(Unexpected, "invalid constructor for this task operation"); } } -QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, +QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const isc::dns::Section& sect, const Op o, const State st) : - qname(n), qclass(c), qtype(RRType::ANY()), section(sect), op(o), - state(st), flags(0) + q(qry), qname(n), qclass(qry.qclass()), qtype(RRType::ANY()), + section(sect), op(o), state(st), flags(0) { if (op != GLUE_QUERY && op != NOGLUE_QUERY) { isc_throw(Unexpected, "invalid constructor for this task operation"); @@ -88,9 +88,9 @@ QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, QueryTask::~QueryTask() {} -Query::Query(Message& m, bool dnssec) : +Query::Query(Message& m, HotCache& c, bool dnssec) : status_(PENDING), qname_(NULL), qclass_(NULL), qtype_(NULL), - message_(&m), want_additional_(true), want_dnssec_(dnssec) + cache_(&c), message_(&m), want_additional_(true), want_dnssec_(dnssec) { // Check message formatting if (message_->getRRCount(Section::QUESTION()) != 1) { @@ -104,7 +104,7 @@ Query::Query(Message& m, bool dnssec) : qtype_ = &question->getType(); restarts_ = 0; - querytasks_.push(QueryTaskPtr(new QueryTask(*qname_, *qclass_, *qtype_, + querytasks_.push(QueryTaskPtr(new QueryTask(*this, *qname_, *qtype_, Section::ANSWER()))); } diff --git a/src/lib/datasrc/query.h b/src/lib/datasrc/query.h index e2624ee8eb..e933025fa2 100644 --- a/src/lib/datasrc/query.h +++ b/src/lib/datasrc/query.h @@ -19,6 +19,9 @@ #include +#include +#include + #include #include #include @@ -27,9 +30,11 @@ #include namespace isc { - namespace datasrc { +class Query; +typedef boost::shared_ptr QueryPtr; + // An individual task to be carried out by the query logic class QueryTask { private: @@ -41,6 +46,9 @@ public: // XXX: Members are currently public, but should probably be // moved to private and wrapped in get() functions later. + // The \c Query that this \c QueryTask was created to service. + const Query& q; + // The standard query tuple: qname/qclass/qtype. // Note that qtype is ignored in the GLUE_QUERY/NOGLUE_QUERY case. const isc::dns::Name qname; @@ -118,15 +126,14 @@ public: uint32_t flags; // Constructors - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, + QueryTask(const Query& q, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect); - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, - const isc::dns::RRType& t, const isc::dns::Section& sect, - Op o); - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, + QueryTask(const Query& q, const isc::dns::Name& n, + const isc::dns::RRType& t, const isc::dns::Section& sect, Op o); + QueryTask(const Query& q, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect, const State st); - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, + QueryTask(const Query& q, const isc::dns::Name& n, const isc::dns::RRType& t, const isc::dns::Section& sect, Op o, State st); @@ -134,12 +141,12 @@ public: // to simplify the code. // // A simple query doesn't need to specify section or state. - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, + QueryTask(const Query& q, const isc::dns::Name& n, const isc::dns::RRType& t, Op o); // A referral query doesn't need to specify section, state, or type. - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, Op o); + QueryTask(const Query& q, const isc::dns::Name& n, Op o); // A glue (or noglue) query doesn't need to specify type. - QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, + QueryTask(const Query& q, const isc::dns::Name& n, const isc::dns::Section& sect, Op o, State st); ~QueryTask(); @@ -148,9 +155,6 @@ public: typedef boost::shared_ptr QueryTaskPtr; typedef std::queue QueryTaskQueue; -class Query; -typedef boost::shared_ptr QueryPtr; - // Data Source query class Query { public: @@ -171,7 +175,7 @@ private: Query& operator=(const Query& source); public: // Query constructor - Query(isc::dns::Message& m, bool dnssec); + Query(isc::dns::Message& m, HotCache& c, bool dnssec); /// \brief The destructor. virtual ~Query(); //@} @@ -179,17 +183,17 @@ public: // wantAdditional() == true indicates that additional-section data // should be looked up while processing this query. false indicates // that we're only interested in answer-section data - bool wantAdditional() { return want_additional_; } + bool wantAdditional() { return (want_additional_); } void setWantAdditional(bool d) { want_additional_ = d; } // wantDnssec() == true indicates that DNSSEC data should be retrieved // from the data source when this query is being processed - bool wantDnssec() const { return want_dnssec_; } + bool wantDnssec() const { return (want_dnssec_); } void setWantDnssec(bool d) { want_dnssec_ = d; } - const isc::dns::Name& qname() const { return *qname_; } - const isc::dns::RRClass& qclass() const { return *qclass_; } - const isc::dns::RRType& qtype() const { return *qtype_; } + const isc::dns::Name& qname() const { return (*qname_); } + const isc::dns::RRClass& qclass() const { return (*qclass_); } + const isc::dns::RRType& qtype() const { return (*qtype_); } // Note: these can't be constant member functions because they expose // writable 'handles' of internal member variables. It's questionable @@ -197,10 +201,10 @@ public: // corresponding members are public (which itself is not a good practice // but it's a different topic), but at the moment we keep them. // We should definitely revisit the design later. - isc::dns::Message& message() { return *message_; } - QueryTaskQueue& tasks() { return querytasks_; } + isc::dns::Message& message() { return (*message_); } + QueryTaskQueue& tasks() { return (querytasks_); } - Status status() const { return status_; } + Status status() const { return (status_); } void setStatus(Status s) { status_ = s; } // Limit CNAME chains to 16 per query, to avoid loops @@ -211,6 +215,13 @@ public: return (false); } + void setDatasrc(DataSrc* ds) { datasrc_ = ds; } + DataSrc* datasrc() const { return (datasrc_); } + + // \brief The query cache. This is a static member of class \c Query; + // the same cache will be used by all instances. + HotCache& getCache() const { return (*cache_); } + private: Status status_; @@ -218,6 +229,9 @@ private: const isc::dns::RRClass* qclass_; const isc::dns::RRType* qtype_; + HotCache* cache_; + DataSrc* datasrc_; + isc::dns::Message* message_; QueryTaskQueue querytasks_; diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc index 6d5edeb1df..5f03e5e6b7 100644 --- a/src/lib/datasrc/sqlite3_datasrc.cc +++ b/src/lib/datasrc/sqlite3_datasrc.cc @@ -344,19 +344,17 @@ Sqlite3DataSrc::findClosest(const Name& name, unsigned int* position) const { } void -Sqlite3DataSrc::findClosestEnclosure(NameMatch& match, - const RRClass& qclass) const -{ - if (qclass != getClass() && qclass != RRClass::ANY()) { +Sqlite3DataSrc::findClosestEnclosure(DataSrcMatch& match) const { + if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) { return; } unsigned int position; - if (findClosest(match.qname(), &position) == -1) { + if (findClosest(match.getName(), &position) == -1) { return; } - match.update(*this, match.qname().split(position)); + match.update(*this, match.getName().split(position)); } DataSrc::Result diff --git a/src/lib/datasrc/sqlite3_datasrc.h b/src/lib/datasrc/sqlite3_datasrc.h index d5ba1d3461..3d263b79b5 100644 --- a/src/lib/datasrc/sqlite3_datasrc.h +++ b/src/lib/datasrc/sqlite3_datasrc.h @@ -58,8 +58,7 @@ public: ~Sqlite3DataSrc(); //@} - void findClosestEnclosure(NameMatch& match, - const isc::dns::RRClass& qclass) const; + void findClosestEnclosure(DataSrcMatch& match) const; Result findRRset(const isc::dns::Name& qname, const isc::dns::RRClass& qclass, diff --git a/src/lib/datasrc/static_datasrc.cc b/src/lib/datasrc/static_datasrc.cc index 758e097381..ada53a2bec 100644 --- a/src/lib/datasrc/static_datasrc.cc +++ b/src/lib/datasrc/static_datasrc.cc @@ -129,11 +129,10 @@ isSubdomain(const Name& qname, const Name& zone) { } void -StaticDataSrc::findClosestEnclosure(NameMatch& match, - const RRClass& qclass) const { - const Name& qname = match.qname(); +StaticDataSrc::findClosestEnclosure(DataSrcMatch& match) const { + const Name& qname = match.getName(); - if (qclass != getClass() && qclass != RRClass::ANY()) { + if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) { return; } diff --git a/src/lib/datasrc/static_datasrc.h b/src/lib/datasrc/static_datasrc.h index 3428279181..129de6b130 100644 --- a/src/lib/datasrc/static_datasrc.h +++ b/src/lib/datasrc/static_datasrc.h @@ -39,8 +39,6 @@ class RRsetList; namespace datasrc { -class Query; -class NameMatch; struct StaticDataSrcImpl; class StaticDataSrc : public DataSrc { @@ -58,8 +56,7 @@ public: ~StaticDataSrc(); //@} - void findClosestEnclosure(NameMatch& match, - const isc::dns::RRClass& qclass) const; + void findClosestEnclosure(DataSrcMatch& match) const; Result findRRset(const isc::dns::Name& qname, const isc::dns::RRClass& qclass, diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am index 5fed72f84d..9c0063db13 100644 --- a/src/lib/datasrc/tests/Makefile.am +++ b/src/lib/datasrc/tests/Makefile.am @@ -16,6 +16,7 @@ run_unittests_SOURCES += datasrc_unittest.cc run_unittests_SOURCES += sqlite3_unittest.cc run_unittests_SOURCES += static_unittest.cc run_unittests_SOURCES += query_unittest.cc +run_unittests_SOURCES += cache_unittest.cc run_unittests_SOURCES += test_datasrc.h test_datasrc.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) diff --git a/src/lib/datasrc/tests/datasrc_unittest.cc b/src/lib/datasrc/tests/datasrc_unittest.cc index dd7a95accb..41cc54369f 100644 --- a/src/lib/datasrc/tests/datasrc_unittest.cc +++ b/src/lib/datasrc/tests/datasrc_unittest.cc @@ -69,6 +69,7 @@ protected: void createAndProcessQuery(const Name& qname, const RRClass& qclass, const RRType& qtype); + HotCache cache; MetaDataSrc meta_source; OutputBuffer obuffer; MessageRenderer renderer; @@ -76,10 +77,10 @@ protected: }; void -performQuery(DataSrc& data_source, Message& message) { +performQuery(DataSrc& data_source, HotCache& cache, Message& message) { message.setHeaderFlag(MessageFlag::AA()); message.setRcode(Rcode::NOERROR()); - Query q(message, true); + Query q(message, cache, true); data_source.doQuery(q); } @@ -92,7 +93,7 @@ DataSrcTest::readAndProcessQuery(const char* datafile) { msg.fromWire(buffer); msg.makeResponse(); - performQuery(meta_source, msg); + performQuery(meta_source, cache, msg); } void @@ -103,7 +104,7 @@ DataSrcTest::createAndProcessQuery(const Name& qname, const RRClass& qclass, msg.setOpcode(Opcode::QUERY()); msg.addQuestion(Question(qname, qclass, qtype)); msg.setHeaderFlag(MessageFlag::RD()); - performQuery(meta_source, msg); + performQuery(meta_source, cache, msg); } void @@ -213,6 +214,83 @@ TEST_F(DataSrcTest, NSQuery) { EXPECT_TRUE(it->isLast()); } +// Make sure two successive queries have the same result +TEST_F(DataSrcTest, DuplicateQuery) { + readAndProcessQuery("q_example_ns"); + headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6); + + RRsetIterator rit = msg.beginSection(Section::ANSWER()); + RRsetPtr rrset = *rit; + EXPECT_EQ(Name("example.com"), rrset->getName()); + EXPECT_EQ(RRType::NS(), rrset->getType()); + EXPECT_EQ(RRClass::IN(), rrset->getClass()); + + RdataIteratorPtr it = rrset->getRdataIterator(); + it->first(); + EXPECT_EQ("dns01.example.com.", it->getCurrent().toText()); + it->next(); + EXPECT_EQ("dns02.example.com.", it->getCurrent().toText()); + it->next(); + EXPECT_EQ("dns03.example.com.", it->getCurrent().toText()); + it->next(); + EXPECT_TRUE(it->isLast()); + + msg.clear(Message::PARSE); + readAndProcessQuery("q_example_ns"); + headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6); + + rit = msg.beginSection(Section::ANSWER()); + rrset = *rit; + EXPECT_EQ(Name("example.com"), rrset->getName()); + EXPECT_EQ(RRType::NS(), rrset->getType()); + EXPECT_EQ(RRClass::IN(), rrset->getClass()); + + it = rrset->getRdataIterator(); + it->first(); + EXPECT_EQ("dns01.example.com.", it->getCurrent().toText()); + it->next(); + EXPECT_EQ("dns02.example.com.", it->getCurrent().toText()); + it->next(); + EXPECT_EQ("dns03.example.com.", it->getCurrent().toText()); + it->next(); + EXPECT_TRUE(it->isLast()); +} + +TEST_F(DataSrcTest, DNSKEYQuery) { + readAndProcessQuery("q_example_dnskey"); + headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6); + + RRsetIterator rit = msg.beginSection(Section::ANSWER()); + RRsetPtr rrset = *rit; + EXPECT_EQ(Name("example.com"), rrset->getName()); + EXPECT_EQ(RRType::DNSKEY(), rrset->getType()); + EXPECT_EQ(RRClass::IN(), rrset->getClass()); +} + +// Repeat the previous query to check that cache is working correctly. +// We query for a record at a zone cut to ensure the REFERRAL flag doesn't +// cause incorrect behavior. +TEST_F(DataSrcTest, DNSKEYDuplicateQuery) { + readAndProcessQuery("q_example_dnskey"); + headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6); + + RRsetIterator rit = msg.beginSection(Section::ANSWER()); + RRsetPtr rrset = *rit; + EXPECT_EQ(Name("example.com"), rrset->getName()); + EXPECT_EQ(RRType::DNSKEY(), rrset->getType()); + EXPECT_EQ(RRClass::IN(), rrset->getClass()); + + msg.clear(Message::PARSE); + readAndProcessQuery("q_example_dnskey"); + headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6); + + rit = msg.beginSection(Section::ANSWER()); + rrset = *rit; + EXPECT_EQ(Name("example.com"), rrset->getName()); + EXPECT_EQ(RRType::DNSKEY(), rrset->getType()); + EXPECT_EQ(RRClass::IN(), rrset->getClass()); +} + TEST_F(DataSrcTest, NxRRset) { readAndProcessQuery("q_example_ptr"); @@ -858,18 +936,6 @@ TEST_F(DataSrcTest, AddRemoveDataSrc) { EXPECT_EQ(0, ds.dataSrcCount()); } -// currently fails -TEST_F(DataSrcTest, DISABLED_synthesizedCnameTooLong) { - // qname has the possible max length (255 octets). it matches a DNAME, - // and the synthesized CNAME would exceed the valid length. - createAndProcessQuery( - Name("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde." - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde." - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde." - "0123456789abcdef0123456789abcdef0123456789a.dname.example.org."), - RRClass::IN(), RRType::A()); -} - TEST_F(DataSrcTest, noNSZone) { EXPECT_THROW(createAndProcessQuery(Name("www.nons.example"), RRClass::IN(), RRType::A()), @@ -888,4 +954,114 @@ TEST_F(DataSrcTest, noSOAZone) { DataSourceError); } +// currently fails +TEST_F(DataSrcTest, DISABLED_synthesizedCnameTooLong) { + // qname has the possible max length (255 octets). it matches a DNAME, + // and the synthesized CNAME would exceed the valid length. + createAndProcessQuery( + Name("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde." + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde." + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde." + "0123456789abcdef0123456789abcdef0123456789a.dname.example.org."), + RRClass::IN(), RRType::A()); +} + +// Tests of the DataSrcMatch class start here +class DataSrcMatchTest : public ::testing::Test { +protected: + DataSrcMatchTest() { + datasrc1.init(); + } + // test data source serves example.com/IN. + TestDataSrc datasrc1; + // this data source is dummy. Its content doesn't matter in the tests. + TestDataSrc datasrc2; +}; + +TEST_F(DataSrcMatchTest, match) { + DataSrcMatch match(Name("very.very.long.example.com"), RRClass::IN()); + datasrc1.findClosestEnclosure(match); + EXPECT_EQ(Name("example.com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); +} + +TEST_F(DataSrcMatchTest, matchWithWrongClass) { + DataSrcMatch match(Name("very.very.long.example.com"), RRClass::CH()); + datasrc1.findClosestEnclosure(match); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); +} + +TEST_F(DataSrcMatchTest, matchWithAnyClass) { + DataSrcMatch match(Name("very.very.long.example.com"), RRClass::ANY()); + datasrc1.findClosestEnclosure(match); + EXPECT_EQ(Name("example.com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); +} + +TEST_F(DataSrcMatchTest, updateWithWrongClass) { + DataSrcMatch match(Name("www.example.com"), RRClass::CH()); + + EXPECT_EQ(RRClass::IN(), datasrc2.getClass()); + match.update(datasrc2, Name("com")); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); + + EXPECT_EQ(RRClass::IN(), datasrc1.getClass()); + match.update(datasrc1, Name("example.com")); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); +} + +TEST_F(DataSrcMatchTest, updateAgainstAnyClass) { + DataSrcMatch match(Name("www.example.com"), RRClass::ANY()); + match.update(datasrc2, Name("com")); + EXPECT_EQ(Name("com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc2, match.getDataSource()); + + // the given class for search is ANY, so update should be okay. + EXPECT_EQ(RRClass::IN(), datasrc1.getClass()); + match.update(datasrc1, Name("example.com")); + EXPECT_EQ(Name("example.com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); +} + +TEST_F(DataSrcMatchTest, updateWithNoMatch) { + DataSrcMatch match(Name("www.example.com"), RRClass::IN()); + match.update(datasrc1, Name("com")); + EXPECT_EQ(Name("com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); + + // An attempt of update with a name that doesn't match. This attempt + // should be ignored. + match.update(datasrc2, Name("example.org")); + EXPECT_EQ(Name("com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); +} + +// This test currently fails. +TEST_F(DataSrcMatchTest, initialUpdateWithNoMatch) { + DataSrcMatch match(Name("www.example.com"), RRClass::IN()); + + // An initial attempt of update with a name that doesn't match. + // Should be ignored. + match.update(datasrc1, Name("example.org")); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); +} + +TEST_F(DataSrcMatchTest, updateWithShorterMatch) { + DataSrcMatch match(Name("www.example.com"), RRClass::IN()); + + match.update(datasrc1, Name("example.com")); + EXPECT_EQ(Name("example.com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); + + // An attempt of update with a name that gives a shorter match. + // This attempt should be ignored. + match.update(datasrc2, Name("com")); + EXPECT_EQ(Name("example.com"), *match.getEnclosingZone()); + EXPECT_EQ(&datasrc1, match.getDataSource()); +} + } diff --git a/src/lib/datasrc/tests/query_unittest.cc b/src/lib/datasrc/tests/query_unittest.cc index d5debcd04f..7cf7fccb12 100644 --- a/src/lib/datasrc/tests/query_unittest.cc +++ b/src/lib/datasrc/tests/query_unittest.cc @@ -16,43 +16,61 @@ #include +#include +#include #include #include #include #include -namespace { +#include +using isc::UnitTestUtil; using namespace isc::dns; using namespace isc::datasrc; +namespace { class QueryTest : public ::testing::Test { protected: - QueryTest() : - name(Name("www.example.com")), - rrtype(RRType::A()), - rrclass(RRClass::IN()) - {} - const Name name; - const RRType rrtype; - const RRClass rrclass; + void readQuery(Message& m, const char* datafile); + + HotCache cache; }; +void +QueryTest::readQuery(Message& m, const char* datafile) { + std::vector data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + m.fromWire(buffer); +} + QueryTaskPtr -createTask(const Name& name, const RRClass& rrclass0, const RRType& rrtype0) { +createTask(Message& m, const Name& name, const RRType& rrtype0, HotCache& c) { RRType rrtype(rrtype0); - return (QueryTaskPtr(new QueryTask(name, rrclass0, rrtype, + Query q(m, c, true); + return (QueryTaskPtr(new QueryTask(q, name, rrtype, QueryTask::SIMPLE_QUERY))); } // Check the QueryTask created using a temporary RRType object will remain // valid. TEST_F(QueryTest, constructWithTemporary) { - QueryTaskPtr task_a = createTask(name, rrclass, RRType::A()); - QueryTaskPtr task_aaaa = createTask(name, rrclass, RRType::AAAA()); - EXPECT_EQ(rrtype, task_a->qtype); + Message m1(Message::PARSE); + readQuery(m1, "q_wild_a"); + QueryTaskPtr task_a = createTask(m1, Name("www.wild.example.com"), + RRType::A(), cache); + EXPECT_EQ(RRType::A(), task_a->qtype); + + Message m2(Message::PARSE); + readQuery(m2, "q_wild_aaaa"); + QueryTaskPtr task_aaaa = createTask(m2, Name("www.wild.example.com"), + RRType::AAAA(), cache); + EXPECT_EQ(RRType::AAAA(), task_aaaa->qtype); + } } diff --git a/src/lib/datasrc/tests/sqlite3_unittest.cc b/src/lib/datasrc/tests/sqlite3_unittest.cc index 66b44db898..3024852da1 100644 --- a/src/lib/datasrc/tests/sqlite3_unittest.cc +++ b/src/lib/datasrc/tests/sqlite3_unittest.cc @@ -374,12 +374,10 @@ TEST_F(Sqlite3DataSourceTest, reOpen) { EXPECT_EQ(DataSrc::SUCCESS, data_source.close()); EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE2)); - NameMatch name_match(www_name); - data_source.findClosestEnclosure(name_match, rrclass); - // XXX: some deviant compilers seem to fail to recognize a NULL as a - // pointer type. This explicit cast works around such compilers. - EXPECT_EQ(static_cast(NULL), name_match.closestName()); - EXPECT_EQ(static_cast(NULL), name_match.bestDataSrc()); + DataSrcMatch match(www_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); } TEST_F(Sqlite3DataSourceTest, openFail) { @@ -415,52 +413,52 @@ TEST_F(Sqlite3DataSourceTest, memoryDB) { } TEST_F(Sqlite3DataSourceTest, findClosestEnclosure) { - NameMatch name_match(www_name); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(zone_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(www_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(zone_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(Sqlite3DataSourceTest, findClosestEnclosureMatchRoot) { EXPECT_EQ(DataSrc::SUCCESS, data_source.close()); EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE_ROOT)); - NameMatch name_match(Name("org.")); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(Name("."), *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(Name("org."), rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(Name("."), *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(Sqlite3DataSourceTest, findClosestEnclosureAtDelegation) { // The search name exists both in the parent and child zones, but // child has a better match. - NameMatch name_match(child_name); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(child_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(child_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(child_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(Sqlite3DataSourceTest, findClosestEnclosureNoMatch) { - NameMatch name_match(nomatch_name); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(static_cast(NULL), name_match.closestName()); - EXPECT_EQ(static_cast(NULL), name_match.bestDataSrc()); + DataSrcMatch match(nomatch_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); } TEST_F(Sqlite3DataSourceTest, findClosestClassMismatch) { - NameMatch name_match(www_name); - data_source.findClosestEnclosure(name_match, rrclass_notmatch); - EXPECT_EQ(static_cast(NULL), name_match.closestName()); - EXPECT_EQ(static_cast(NULL), name_match.bestDataSrc()); + DataSrcMatch match(nomatch_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); } // If the query class is ANY, the result should be the same as the case where // the class exactly matches. TEST_F(Sqlite3DataSourceTest, findClosestClassAny) { - NameMatch name_match(www_name); - data_source.findClosestEnclosure(name_match, RRClass::ANY()); - EXPECT_EQ(zone_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(www_name, RRClass::ANY()); + data_source.findClosestEnclosure(match); + EXPECT_EQ(zone_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(Sqlite3DataSourceTest, findRRsetNormal) { diff --git a/src/lib/datasrc/tests/static_unittest.cc b/src/lib/datasrc/tests/static_unittest.cc index 583775f579..f30de12598 100644 --- a/src/lib/datasrc/tests/static_unittest.cc +++ b/src/lib/datasrc/tests/static_unittest.cc @@ -196,55 +196,54 @@ TEST_F(StaticDataSourceTest, close) { } TEST_F(StaticDataSourceTest, findClosestEnclosureForVersion) { - NameMatch name_match(version_name); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(version_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(version_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(version_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } // Class Any query should result in the same answer. TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionClassAny) { - NameMatch name_match(version_name); - data_source.findClosestEnclosure(name_match, RRClass::ANY()); - EXPECT_EQ(version_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(version_name, RRClass::ANY()); + data_source.findClosestEnclosure(match); + EXPECT_EQ(version_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } // If class doesn't match the lookup should fail. TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionClassMismatch) { - NameMatch name_match(version_name); - data_source.findClosestEnclosure(name_match, RRClass::IN()); - // XXX: see sqlite3_unittest.cc about the cast. - EXPECT_EQ(static_cast(NULL), name_match.closestName()); - EXPECT_EQ(static_cast(NULL), name_match.bestDataSrc()); + DataSrcMatch match(version_name, RRClass::IN()); + data_source.findClosestEnclosure(match); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); } TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionPartial) { - NameMatch name_match(Name("foo").concatenate(version_name)); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(version_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(Name("foo").concatenate(version_name), rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(version_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(StaticDataSourceTest, findClosestEnclosureForAuthors) { - NameMatch name_match(authors_name); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(authors_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(authors_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(authors_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(StaticDataSourceTest, findClosestEnclosureForAuthorsPartial) { - NameMatch name_match(Name("foo").concatenate(authors_name)); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(authors_name, *name_match.closestName()); - EXPECT_EQ(&data_source, name_match.bestDataSrc()); + DataSrcMatch match(Name("foo").concatenate(authors_name), rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(authors_name, *match.getEnclosingZone()); + EXPECT_EQ(&data_source, match.getDataSource()); } TEST_F(StaticDataSourceTest, findClosestEnclosureNoMatch) { - NameMatch name_match(nomatch_name); - data_source.findClosestEnclosure(name_match, rrclass); - EXPECT_EQ(static_cast(NULL), name_match.closestName()); - EXPECT_EQ(static_cast(NULL), name_match.bestDataSrc()); + DataSrcMatch match(nomatch_name, rrclass); + data_source.findClosestEnclosure(match); + EXPECT_EQ(NULL, match.getEnclosingZone()); + EXPECT_EQ(NULL, match.getDataSource()); } TEST_F(StaticDataSourceTest, findRRsetVersionTXT) { diff --git a/src/lib/datasrc/tests/test_datasrc.cc b/src/lib/datasrc/tests/test_datasrc.cc index d92a2da060..d799084571 100644 --- a/src/lib/datasrc/tests/test_datasrc.cc +++ b/src/lib/datasrc/tests/test_datasrc.cc @@ -65,8 +65,9 @@ namespace { // {"example.com", "AAAA", "2001:db8::2"}, // ... // If an RRset is associated with an RRSIG, the RRSIG must immediately follow -// the RRset to be signed. Currently, only one (or zero) RRSIG can be -// specified per RRset. +// the RRset to be signed. Multiple RRSIGs can follow the RRset. RRSIG +// records will always be attached to the most recent non-RRSIG RRset; +// consequently, the first RR listed must not be an RRSIG record. // // Names are sorted internally, and don't have to be sorted in the data. // @@ -107,6 +108,10 @@ const struct RRData example_com_records[] = { {"example.com", "RRSIG", "SOA 5 2 3600 20100322084538 20100220084538 33495 example.com. KUun66Qaw36osk2BJS6U1fAy3PPDkNo2QK4meGNbDBY8q8b+f2o+IXJ14YCvssGl1ORW0CcLnDRxssnk8V/Svmj5iFhO+8HC2hnVBdi2zewvdVtwRb+lWwKN7pkXXwuy6g1t9WCd/j5FCc/wgxqtZUTPb6XgZcnHrORDMOTqLs4="}, {"example.com", "NSEC", "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY"}, {"example.com", "RRSIG", "NSEC 5 2 7200 20100322084538 20100220084538 33495 example.com. KxuVaPPKNPJzr/q+cJPiNlkHVTQK0LVsgTbSqruXQc25lAd0wn5oKUtxL1bEAchHkfA8eLzcYCj2ZqqAv9OJubw53mfskTad7UHs4Uj2RTrIsNGMCiZGgOpvNb9JcWpQtoyXVT1uNse+Qsbeir0eyeYIufUynFU041jtNrlJMio="}, + {"example.com", "DNSKEY", "257 3 5 AwEAAe5WFbxdCPq2jZrZhlMj7oJdff3W7syJtbvzg62tRx0gkoCDoBI9DPjlOQG0UAbj+xUV4HQZJStJaZ+fHU5AwVNT+bBZdtV+NujSikhdTHb4FYLg2b3Cx9NyJvAVukHp/91HnWuG4T36CzAFrfPwsHIrBz9BsaIQ21VRkcmj7DswfI/iDGd8j6bqiODyNZYQ+ZrLmF0KIJ2yPN3iO6Zq23TaOrVTjB7d1a/h31ODfiHAxFHrkY3t3D5JR9Nsl/7fdRmSznwtcSDgLXBoFEYmw6p86AcvRyoYNcL1SXjaKVLG5jyU3UR+LcGZT5t/0xGfoIK/aKwENrsjcKZZj660b1M="}, + {"example.com", "DNSKEY", "256 3 5 AwEAAcOUBllYc1hf7ND9uDy+Yz1BF3sI0m4qNGV7WcTD0WEiuV7IjXgHE36fCmS9QsUxSSOVo1I/FMxI2PJVqTYHkXFBS7AzLGsQYMU7UjBZSotBJ6Imt5pXMu+lEDNy8TOUzG3xm7g0qcbWYF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx"}, + {"example.com", "RRSIG", "DNSKEY 5 2 3600 20100416210049 20100317210049 4456 example.com. 37FC0rcwOZVarTMjft0BMbvv8hbJU7OHNsvO7R1q6OgsLTj7QGMX3sC42JGbwUrYI/OwnZblNcv1eim0g0jX5k+sVr2OJsEubngRjVqLo54qV8rBC14tLk9PGKxxjQG0IBJU866uHxzXYBO2a1r2g93/qyTtrT7iPLu/2Ce1WRKMBPK0yf4nW2usFU/PXesXFWpZ7HLGZL73/NWv8wcezBDuU0B2PlHLjSu7k6poq6JWDC02o5SYnEBwsJ5Chi+3/NZmzKTiNP7g0H4t6QhunkEXxL3z0617mwwQt00ypXsNunnPy4Ub5Kllk1SKJl8ZkEDKkJtSvuXJhcAZsLyMQw=="}, + {"example.com", "RRSIG", "DNSKEY 5 2 3600 20100416210049 20100317210049 33495 example.com. h3OM5r3roBsgnEQk9fcjTg5L7p3yDptDpVzDN/lgjqpaWxtlz5LsulBH3YzwYyXzT7pG7L0/qT6dcuRECc/rniECviWvmJMJZzEAMry0Of/pk/8ekuGTxABpqwAoCwM5as30sc0cfMJTS7umpJVDA4lRB2zoKGefWnJ3+pREDiY="}, // dns01.example.com {"dns01.example.com", "A", "192.0.2.1"}, @@ -335,8 +340,9 @@ buildZone(Zone& zone, const RRData* records, const bool is_glue) { rrset->addRdata(createRdata(rrtype, zone.rrclass, records[i].rdata)); if (rrtype == RRType::RRSIG()) { prev_rrset->addRRsig(rrset); + } else { + prev_rrset = rrset; } - prev_rrset = rrset; } } @@ -361,12 +367,10 @@ TestDataSrc::init() { } void -TestDataSrc::findClosestEnclosure(NameMatch& match, - const RRClass& qclass) const -{ - const Name& qname = match.qname(); +TestDataSrc::findClosestEnclosure(DataSrcMatch& match) const { + const Name& qname = match.getName(); - if (qclass != getClass() && qclass != RRClass::ANY()) { + if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) { return; } diff --git a/src/lib/datasrc/tests/test_datasrc.h b/src/lib/datasrc/tests/test_datasrc.h index b3ed55a986..eb417cf041 100644 --- a/src/lib/datasrc/tests/test_datasrc.h +++ b/src/lib/datasrc/tests/test_datasrc.h @@ -48,8 +48,7 @@ public: ~TestDataSrc() {} //@} - void findClosestEnclosure(NameMatch& match, - const isc::dns::RRClass& qclass) const; + void findClosestEnclosure(DataSrcMatch& match) const; Result findRRset(const isc::dns::Name& qname, const isc::dns::RRClass& qclass, diff --git a/src/lib/dns/question.h b/src/lib/dns/question.h index e684871bc6..3d7217383d 100644 --- a/src/lib/dns/question.h +++ b/src/lib/dns/question.h @@ -229,6 +229,20 @@ public: unsigned int toWire(OutputBuffer& buffer) const; //@} + /// + /// \name Comparison Operator + /// + //@{ + /// A comparison operator is needed for this class so it can + /// function as an index to std::map. + bool operator <(const Question& rhs) const { + return (rrclass_ < rhs.rrclass_ || + (rrclass_ == rhs.rrclass_ && + (rrtype_ < rhs.rrtype_ || + (rrtype_ == rhs.rrtype_ && (name_ < rhs.name_))))); + } + //@} + private: Name name_; RRType rrtype_; diff --git a/src/lib/dns/rrsetlist.cc b/src/lib/dns/rrsetlist.cc index 1000799829..84e2ce5d15 100644 --- a/src/lib/dns/rrsetlist.cc +++ b/src/lib/dns/rrsetlist.cc @@ -40,6 +40,14 @@ RRsetList::addRRset(RRsetPtr rrsetptr) { rrsets_.push_back(rrsetptr); } +void +RRsetList::append(RRsetList& source) +{ + BOOST_FOREACH(RRsetPtr rrset, source) { + addRRset(rrset); + } +} + RRsetPtr RRsetList::findRRset(const RRType& rrtype, const RRClass& rrclass) { BOOST_FOREACH(RRsetPtr rrsetptr, rrsets_) { diff --git a/src/lib/dns/rrsetlist.h b/src/lib/dns/rrsetlist.h index a8c3769d9b..a07f0fc6ad 100644 --- a/src/lib/dns/rrsetlist.h +++ b/src/lib/dns/rrsetlist.h @@ -82,6 +82,7 @@ private: public: RRsetList() {} void addRRset(RRsetPtr new_rrsetptr); + void append(RRsetList& source); RRsetPtr findRRset(const RRType& rrtype, const RRClass& rrclass); typedef RRsetListIterator::iterator, diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc index 50bae63d64..06670b42c9 100644 --- a/src/lib/dns/tests/question_unittest.cc +++ b/src/lib/dns/tests/question_unittest.cc @@ -119,4 +119,35 @@ TEST_F(QuestionTest, LeftShiftOperator) oss << test_question1; EXPECT_EQ(test_question1.toText(), oss.str()); } + +TEST_F(QuestionTest, comparison) +{ + const Name a("a"), b("b"); + const RRClass in(RRClass::IN()), ch(RRClass::CH()); + const RRType ns(RRType::NS()), aaaa(RRType::AAAA()); + + EXPECT_TRUE(Question(a, in, ns) < Question(a, in, aaaa)); + EXPECT_FALSE(Question(a, in, aaaa) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, ns)); + EXPECT_FALSE(Question(a, ch, ns) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, aaaa)); + EXPECT_FALSE(Question(a, ch, aaaa) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(b, in, ns)); + EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, ns)); + EXPECT_FALSE(Question(b, ch, ns) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, aaaa)); + EXPECT_FALSE(Question(b, ch, aaaa) < Question(a, in, ns)); + + EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns)); + EXPECT_FALSE(Question(a, ch, ns) < Question(a, ch, ns)); + EXPECT_FALSE(Question(b, in, ns) < Question(b, in, ns)); + EXPECT_FALSE(Question(b, in, aaaa) < Question(b, in, aaaa)); +} + } diff --git a/src/lib/dns/tests/rrsetlist_unittest.cc b/src/lib/dns/tests/rrsetlist_unittest.cc index 049b79c067..51da4df57a 100644 --- a/src/lib/dns/tests/rrsetlist_unittest.cc +++ b/src/lib/dns/tests/rrsetlist_unittest.cc @@ -48,6 +48,7 @@ const generic::NS rdata_ns("ns.example.com"); const generic::SOA rdata_soa(Name("ns.example.com"), Name("root.example.com"), 2010012601, 3600, 300, 3600000, 1200); const generic::CNAME rdata_cname("target.example.com"); +const generic::DNAME rdata_dname("dtarget.example.com"); void RRsetListTest::setupList(RRsetList& list) { @@ -86,6 +87,24 @@ TEST_F(RRsetListTest, addRRsets) { EXPECT_EQ(list.size(), 5); } +TEST_F(RRsetListTest, append) { + RRsetList list1; + setupList(list1); + RRsetList list2; + RRsetPtr dname(new RRset(Name("example.com"), RRClass::IN(), + RRType::DNAME(), example_ttl)); + dname->addRdata(rdata_dname); + list2.addRRset(dname); + list1.append(list2); + EXPECT_EQ(list2.size(), 1); + EXPECT_EQ(list1.size(), 6); + + RRsetPtr rrset = list1.findRRset(RRType::DNAME(), RRClass::IN()); + EXPECT_EQ(RRType::DNAME(), rrset->getType()); + + EXPECT_THROW(list1.append(list2), DuplicateRRset); +} + TEST_F(RRsetListTest, extraRRset) { RRsetList list; setupList(list);