[svn] commit: r4110 - in /branches/trac453: ./ src/bin/auth/ src/bin/auth/benchmarks/ src/bin/auth/tests/ src/bin/auth/tests/testdata/ src/bin/bind10/ src/bin/recurse/tests/ src/lib/asiolink/ src/lib/datasrc/ src/lib/datasrc/tests/ src/lib/log/ src/lib/python/isc/utils/ src/lib/testutils/ src/lib/testutils/testdata/
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Dec 31 03:18:44 UTC 2010
Author: chenzhengzhang
Date: Fri Dec 31 03:18:44 2010
New Revision: 4110
Log:
sync with trunk
Added:
branches/trac453/src/lib/testutils/testdata/iquery_fromWire.spec
- copied unchanged from r4109, trunk/src/lib/testutils/testdata/iquery_fromWire.spec
branches/trac453/src/lib/testutils/testdata/iquery_response_fromWire.spec
- copied unchanged from r4109, trunk/src/lib/testutils/testdata/iquery_response_fromWire.spec
Modified:
branches/trac453/ (props changed)
branches/trac453/ChangeLog
branches/trac453/src/bin/auth/auth_srv.cc
branches/trac453/src/bin/auth/benchmarks/query_bench.cc
branches/trac453/src/bin/auth/query.cc
branches/trac453/src/bin/auth/query.h
branches/trac453/src/bin/auth/tests/auth_srv_unittest.cc
branches/trac453/src/bin/auth/tests/query_unittest.cc
branches/trac453/src/bin/auth/tests/testdata/ (props changed)
branches/trac453/src/bin/bind10/bind10.py.in (props changed)
branches/trac453/src/bin/recurse/tests/ (props changed)
branches/trac453/src/lib/asiolink/ (props changed)
branches/trac453/src/lib/datasrc/memory_datasrc.cc
branches/trac453/src/lib/datasrc/memory_datasrc.h
branches/trac453/src/lib/datasrc/rbtree.h
branches/trac453/src/lib/datasrc/tests/memory_datasrc_unittest.cc
branches/trac453/src/lib/datasrc/tests/rbtree_unittest.cc
branches/trac453/src/lib/datasrc/zone.h
branches/trac453/src/lib/datasrc/zonetable.h
branches/trac453/src/lib/log/ (props changed)
branches/trac453/src/lib/python/isc/utils/ (props changed)
branches/trac453/src/lib/testutils/ (props changed)
branches/trac453/src/lib/testutils/testdata/Makefile.am
Modified: branches/trac453/ChangeLog
==============================================================================
--- branches/trac453/ChangeLog (original)
+++ branches/trac453/ChangeLog Fri Dec 31 03:18:44 2010
@@ -1,3 +1,15 @@
+ 142. [func] jinmei
+ b10-auth: updated query benchmark so that it can test in memory
+ data source. Also fixed a bug that the output buffer isn't
+ cleared after query processing, resulting in misleading results
+ or program crash. This is a regression due to change #135.
+ (Trac #465, svn r4103)
+
+ 141. [bug] jinmei
+ b10-auth: Fixed a bug that the authoritative server includes
+ trailing garbage data in responses. This is a regression due to
+ change #135. (Trac #462, svn r4081)
+
140. [func] y-aharen
src/bin/auth: Added a feature to count queries and send counter
values to statistics periodically. To support it, added wrapping
Modified: branches/trac453/src/bin/auth/auth_srv.cc
==============================================================================
--- branches/trac453/src/bin/auth/auth_srv.cc (original)
+++ branches/trac453/src/bin/auth/auth_srv.cc Fri Dec 31 03:18:44 2010
@@ -162,33 +162,20 @@
AuthSrv* server_;
};
-// This is a derived class of \c DNSAnswer, to serve as a
-// callback in the asiolink module. It takes a completed
-// set of answer data from the DNS lookup and assembles it
-// into a wire-format response.
+// This is a derived class of \c DNSAnswer, to serve as a callback in the
+// asiolink module. We actually shouldn't do anything in this class because
+// we build complete response messages in the process methods; otherwise
+// the response message will contain trailing garbage. In future, we should
+// probably even drop the reliance on DNSAnswer. We don't need the coroutine
+// tricks provided in that framework, and its overhead would be significant
+// in terms of performance consideration for the authoritative server
+// implementation.
class MessageAnswer : public DNSAnswer {
public:
- MessageAnswer(AuthSrv* srv) : server_(srv) {}
- virtual void operator()(const IOMessage& io_message, MessagePtr message,
- OutputBufferPtr buffer) const
- {
- MessageRenderer renderer(*buffer);
- if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
- ConstEDNSPtr edns(message->getEDNS());
- renderer.setLengthLimit(edns ? edns->getUDPSize() :
- Message::DEFAULT_MAX_UDPSIZE);
- } else {
- renderer.setLengthLimit(65535);
- }
- message->toWire(renderer);
- if (server_->getVerbose()) {
- cerr << "[b10-auth] sending a response (" << renderer.getLength()
- << " bytes):\n" << message->toText() << endl;
- }
- }
-
-private:
- AuthSrv* server_;
+ MessageAnswer(AuthSrv*) {}
+ virtual void operator()(const IOMessage&, MessagePtr,
+ OutputBufferPtr) const
+ {}
};
// This is a derived class of \c SimpleCallback, to serve
Modified: branches/trac453/src/bin/auth/benchmarks/query_bench.cc
==============================================================================
--- branches/trac453/src/bin/auth/benchmarks/query_bench.cc (original)
+++ branches/trac453/src/bin/auth/benchmarks/query_bench.cc Fri Dec 31 03:18:44 2010
@@ -33,6 +33,7 @@
#include <xfr/xfrout_client.h>
#include <auth/auth_srv.h>
+#include <auth/config.h>
#include <auth/query.h>
#include <asiolink/asiolink.h>
@@ -53,24 +54,25 @@
// Just something to pass as the server to resume
class DummyServer : public DNSServer {
public:
- virtual void operator()(asio::error_code, size_t) { }
- virtual void resume(const bool) { }
+ virtual void operator()(asio::error_code, size_t) {}
+ virtual void resume(const bool) {}
virtual DNSServer* clone() {
- return new DummyServer(*this);
+ return (new DummyServer(*this));
}
};
class QueryBenchMark {
-private:
+protected:
// Maintain dynamically generated objects via shared pointers because
// QueryBenchMark objects will be copied.
typedef boost::shared_ptr<AuthSrv> AuthSrvPtr;
+private:
typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
-public:
- QueryBenchMark(const int cache_slots, const char* const datasrc_file,
+protected:
+ QueryBenchMark(const bool enable_cache,
const BenchQueries& queries, MessagePtr query_message,
OutputBufferPtr buffer) :
- server_(new AuthSrv(cache_slots >= 0 ? true : false, xfrout_client)),
+ server_(new AuthSrv(enable_cache, xfrout_client)),
queries_(queries),
query_message_(query_message),
buffer_(buffer),
@@ -78,13 +80,8 @@
dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
IOAddress("192.0.2.1"),
5300)))
- {
- if (cache_slots >= 0) {
- server_->setCacheSlots(cache_slots);
- }
- server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
- string(datasrc_file) + "\"}"));
- }
+ {}
+public:
unsigned int run() {
BenchQueries::const_iterator query;
const BenchQueries::const_iterator query_end = queries_.end();
@@ -93,14 +90,16 @@
IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
*dummy_endpoint);
query_message_->clear(Message::PARSE);
+ buffer_->clear();
server_->processMessage(io_message, query_message_, buffer_,
- &server);
+ &server);
}
return (queries_.size());
}
+protected:
+ AuthSrvPtr server_;
private:
- AuthSrvPtr server_;
const BenchQueries& queries_;
MessagePtr query_message_;
OutputBufferPtr buffer_;
@@ -108,26 +107,92 @@
IOEndpointPtr dummy_endpoint;
};
+class Sqlite3QueryBenchMark : public QueryBenchMark {
+public:
+ Sqlite3QueryBenchMark(const int cache_slots,
+ const char* const datasrc_file,
+ const BenchQueries& queries,
+ MessagePtr query_message,
+ OutputBufferPtr buffer) :
+ QueryBenchMark(cache_slots >= 0 ? true : false, queries,
+ query_message, buffer)
+ {
+ if (cache_slots >= 0) {
+ server_->setCacheSlots(cache_slots);
+ }
+ server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
+ string(datasrc_file) + "\"}"));
+ }
+};
+
+class MemoryQueryBenchMark : public QueryBenchMark {
+public:
+ MemoryQueryBenchMark(const char* const zone_file,
+ const char* const zone_origin,
+ const BenchQueries& queries,
+ MessagePtr query_message,
+ OutputBufferPtr buffer) :
+ QueryBenchMark(false, queries, query_message, buffer)
+ {
+ configureAuthServer(*server_,
+ Element::fromJSON(
+ "{\"datasources\": "
+ " [{\"type\": \"memory\","
+ " \"zones\": [{\"origin\": \"" +
+ string(zone_origin) + "\","
+ " \"file\": \"" +
+ string(zone_file) + "\"}]}]}"));
+ }
+};
+
+void
+printQPSResult(unsigned int iteration, double duration,
+ double iteration_per_second)
+{
+ cout.precision(6);
+ cout << "Processed " << iteration << " queries in "
+ << fixed << duration << "s";
+ cout.precision(2);
+ cout << " (" << fixed << iteration_per_second << "qps)" << endl;
+}
}
namespace isc {
namespace bench {
template<>
void
-BenchMark<QueryBenchMark>::printResult() const {
- cout.precision(6);
- cout << "Processed " << getIteration() << " queries in "
- << fixed << getDuration() << "s";
- cout.precision(2);
- cout << " (" << fixed << getIterationPerSecond() << "qps)" << endl;
+BenchMark<Sqlite3QueryBenchMark>::printResult() const {
+ printQPSResult(getIteration(), getDuration(), getIterationPerSecond());
+}
+
+template<>
+void
+BenchMark<MemoryQueryBenchMark>::printResult() const {
+ printQPSResult(getIteration(), getDuration(), getIterationPerSecond());
}
}
}
namespace {
+const int ITERATION_DEFAULT = 1;
+enum DataSrcType {
+ SQLITE3,
+ MEMORY
+};
+
void
usage() {
- cerr << "Usage: query_bench [-n iterations] datasrc_file query_datafile"
+ cerr <<
+ "Usage: query_bench [-n iterations] [-t datasrc_type] [-o origin] "
+ "datasrc_file query_datafile\n"
+ " -n Number of iterations per test case (default: "
+ << ITERATION_DEFAULT << ")\n"
+ " -t Type of data source: sqlite3|memory (default: sqlite3)\n"
+ " -o Origin name of datasrc_file necessary for \"memory\", "
+ "ignored for others\n"
+ " datasrc_file: sqlite3 DB file for \"sqlite3\", "
+ "textual master file for \"memory\" datasrc\n"
+ " query_datafile: queryperf style input data"
<< endl;
exit (1);
}
@@ -136,12 +201,20 @@
int
main(int argc, char* argv[]) {
int ch;
- int iteration = 1;
- while ((ch = getopt(argc, argv, "n:")) != -1) {
+ int iteration = ITERATION_DEFAULT;
+ const char* opt_datasrc_type = "sqlite3";
+ const char* origin = NULL;
+ while ((ch = getopt(argc, argv, "n:t:o:")) != -1) {
switch (ch) {
case 'n':
iteration = atoi(optarg);
break;
+ case 't':
+ opt_datasrc_type = optarg;
+ break;
+ case 'o':
+ origin = optarg;
+ break;
case '?':
default:
usage();
@@ -155,6 +228,21 @@
const char* const datasrc_file = argv[0];
const char* const query_data_file = argv[1];
+ DataSrcType datasrc_type = SQLITE3;
+ if (strcmp(opt_datasrc_type, "sqlite3") == 0) {
+ ; // no need to override
+ } else if (strcmp(opt_datasrc_type, "memory") == 0) {
+ datasrc_type = MEMORY;
+ } else {
+ cerr << "Unknown data source type: " << datasrc_type << endl;
+ return (1);
+ }
+
+ if (datasrc_type == MEMORY && origin == NULL) {
+ cerr << "'-o Origin' is missing for memory data source " << endl;
+ return (1);
+ }
+
BenchQueries queries;
loadQueryData(query_data_file, queries, RRClass::IN());
OutputBufferPtr buffer(new OutputBuffer(4096));
@@ -162,32 +250,46 @@
cout << "Parameters:" << endl;
cout << " Iterations: " << iteration << endl;
- cout << " Data Source: " << datasrc_file << endl;
+ cout << " Data Source: type=" << opt_datasrc_type << ", file=" <<
+ datasrc_file << endl;
+ if (origin != NULL) {
+ cout << " Origin: " << origin << endl;
+ }
cout << " Query data: file=" << query_data_file << " (" << queries.size()
<< " queries)" << endl << endl;
- cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
- << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(0, datasrc_file, queries, message,
- buffer));
-
- cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
- << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(10 * queries.size(), datasrc_file,
+ switch (datasrc_type) {
+ case SQLITE3:
+ cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
+ << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(0, datasrc_file, queries,
+ message, buffer));
+
+ cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
+ << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(10 * queries.size(), datasrc_file,
queries, message, buffer));
- cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
- << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(queries.size() / 2, datasrc_file,
+ cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
+ << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(queries.size() / 2, datasrc_file,
queries, message, buffer));
- cout << "Benchmark disabling Hot Spot Cache" << endl;
- BenchMark<QueryBenchMark>(iteration,
- QueryBenchMark(-1, datasrc_file, queries,
- message, buffer));
+ cout << "Benchmark disabling Hot Spot Cache" << endl;
+ BenchMark<Sqlite3QueryBenchMark>(
+ iteration, Sqlite3QueryBenchMark(-1, datasrc_file, queries,
+ message, buffer));
+ break;
+ case MEMORY:
+ cout << "Benchmark with In Memory Data Source" << endl;
+ BenchMark<MemoryQueryBenchMark>(
+ iteration, MemoryQueryBenchMark(datasrc_file, origin, queries,
+ message, buffer));
+ break;
+ }
return (0);
}
Modified: branches/trac453/src/bin/auth/query.cc
==============================================================================
--- branches/trac453/src/bin/auth/query.cc (original)
+++ branches/trac453/src/bin/auth/query.cc Fri Dec 31 03:18:44 2010
@@ -68,6 +68,24 @@
}
void
+Query::putSOA(const Zone& zone) const {
+ Zone::FindResult soa_result(zone.find(zone.getOrigin(),
+ RRType::SOA()));
+ if (soa_result.code != Zone::SUCCESS) {
+ isc_throw(NoSOA, "There's no SOA record in zone " <<
+ zone.getOrigin().toText());
+ } else {
+ /*
+ * FIXME:
+ * The const-cast is wrong, but the Message interface seems
+ * to insist.
+ */
+ response_.addRRset(Message::SECTION_AUTHORITY,
+ boost::const_pointer_cast<RRset>(soa_result.rrset));
+ }
+}
+
+void
Query::process() const {
bool keep_doing = true;
response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
@@ -105,12 +123,14 @@
getAdditional(*result.zone, *db_result.rrset);
break;
case Zone::NXDOMAIN:
+ // Just empty answer with SOA in authority section
response_.setRcode(Rcode::NXDOMAIN());
- // TODO : add SOA to authority section
+ putSOA(*result.zone);
break;
case Zone::NXRRSET:
+ // Just empty answer with SOA in authority section
response_.setRcode(Rcode::NOERROR());
- // TODO : add SOA to authority section
+ putSOA(*result.zone);
break;
case Zone::CNAME:
case Zone::DNAME:
Modified: branches/trac453/src/bin/auth/query.h
==============================================================================
--- branches/trac453/src/bin/auth/query.h (original)
+++ branches/trac453/src/bin/auth/query.h Fri Dec 31 03:18:44 2010
@@ -13,6 +13,8 @@
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
+
+#include <exceptions/exceptions.h>
namespace isc {
namespace dns {
@@ -132,15 +134,46 @@
/// providing compatible behavior may have its own benefit, so this point
/// should be revisited later.
///
- /// Right now this method never throws an exception, but it may in a
- /// future version.
+ /// This might throw BadZone or any of its specific subclasses, but that
+ /// shouldn't happen in real-life (as BadZone means wrong data, it should
+ /// have been rejected upon loading).
void process() const;
+
+ /// \short Bad zone data encountered.
+ ///
+ /// This is thrown when process encounteres misconfigured zone in a way
+ /// it can't continue. This throws, not sets the Rcode, because such
+ /// misconfigured zone should not be present in the data source and
+ /// should have been rejected sooner.
+ struct BadZone : public isc::Exception {
+ BadZone(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+ };
+
+ /// \short Zone is missing its SOA record.
+ ///
+ /// We tried to add a SOA into the authoritative section, but the zone
+ /// does not contain one.
+ struct NoSOA : public BadZone {
+ NoSOA(const char* file, size_t line, const char* what) :
+ BadZone(file, line, what)
+ {}
+ };
private:
const isc::datasrc::MemoryDataSrc& memory_datasrc_;
const isc::dns::Name& qname_;
const isc::dns::RRType& qtype_;
isc::dns::Message& response_;
+
+ /**
+ * \short Adds a SOA.
+ *
+ * Adds a SOA of the zone into the authority zone of response_.
+ * Can throw NoSOA.
+ */
+ void putSOA(const isc::datasrc::Zone& zone) const;
};
}
Modified: branches/trac453/src/bin/auth/tests/auth_srv_unittest.cc
==============================================================================
--- branches/trac453/src/bin/auth/tests/auth_srv_unittest.cc (original)
+++ branches/trac453/src/bin/auth/tests/auth_srv_unittest.cc Fri Dec 31 03:18:44 2010
@@ -15,6 +15,19 @@
// $Id$
#include <config.h>
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
#include <testutils/srv_unittest.h>
@@ -22,6 +35,7 @@
using namespace isc::cc;
using namespace isc::dns;
+using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::xfr;
using namespace asiolink;
@@ -45,7 +59,103 @@
MockXfroutClient xfrout;
AuthSrv server;
const RRClass rrclass;
+ vector<uint8_t> response_data;
};
+
+// A helper function that builds a response to version.bind/TXT/CH that
+// should be identical to the response from our builtin (static) data source
+// by default. The resulting wire-format data will be stored in 'data'.
+void
+createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
+ const Name version_name("version.bind");
+ Message message(Message::RENDER);
+
+ UnitTestUtil::createRequestMessage(message, Opcode::QUERY(),
+ qid, version_name,
+ RRClass::CH(), RRType::TXT());
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
+ RRType::TXT(), RRTTL(0)));
+ rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
+ message.addRRset(Message::SECTION_ANSWER, rrset_version);
+
+ RRsetPtr rrset_version_ns = RRsetPtr(new RRset(version_name, RRClass::CH(),
+ RRType::NS(), RRTTL(0)));
+ rrset_version_ns->addRdata(generic::NS(version_name));
+ message.addRRset(Message::SECTION_AUTHORITY, rrset_version_ns);
+
+ OutputBuffer obuffer(0);
+ MessageRenderer renderer(obuffer);
+ message.toWire(renderer);
+
+ data.clear();
+ data.assign(static_cast<const uint8_t*>(renderer.getData()),
+ static_cast<const uint8_t*>(renderer.getData()) +
+ renderer.getLength());
+}
+
+// In the following tests we confirm the response data is rendered in
+// wire format in the expected way.
+
+// The most primitive check: checking the result of the processMessage()
+// method
+TEST_F(AuthSrvTest, builtInQuery) {
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("version.bind"),
+ RRClass::CH(), RRType::TXT());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ createBuiltinVersionResponse(default_qid, response_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ response_obuffer->getData(),
+ response_obuffer->getLength(),
+ &response_data[0], response_data.size());
+}
+
+// Same test emulating the UDPServer class behavior (defined in libasiolink).
+// This is not a good test in that it assumes internal implementation details
+// of UDPServer, but we've encountered a regression due to the introduction
+// of that class, so we add a test for that case to prevent such a regression
+// in future.
+// Besides, the generalization of UDPServer is probably too much for the
+// authoritative only server in terms of performance, and it's quite likely
+// we need to drop it for the authoritative server implementation.
+// At that point we can drop this test, too.
+TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("version.bind"),
+ RRClass::CH(), RRType::TXT());
+ createRequestPacket(request_message, IPPROTO_UDP);
+
+ (*server.getDNSLookupProvider())(*io_message, parse_message,
+ response_obuffer, &dnsserv);
+ (*server.getDNSAnswerProvider())(*io_message, parse_message,
+ response_obuffer);
+
+ createBuiltinVersionResponse(default_qid, response_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ response_obuffer->getData(),
+ response_obuffer->getLength(),
+ &response_data[0], response_data.size());
+}
+
+// Same type of test as builtInQueryViaDNSServer but for an error response.
+TEST_F(AuthSrvTest, iqueryViaDNSServer) {
+ createDataFromFile("iquery_fromWire.wire");
+ (*server.getDNSLookupProvider())(*io_message, parse_message,
+ response_obuffer, &dnsserv);
+ (*server.getDNSAnswerProvider())(*io_message, parse_message,
+ response_obuffer);
+
+ UnitTestUtil::readWireData("iquery_response_fromWire.wire",
+ response_data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ response_obuffer->getData(),
+ response_obuffer->getLength(),
+ &response_data[0], response_data.size());
+}
// Unsupported requests. Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
Modified: branches/trac453/src/bin/auth/tests/query_unittest.cc
==============================================================================
--- branches/trac453/src/bin/auth/tests/query_unittest.cc (original)
+++ branches/trac453/src/bin/auth/tests/query_unittest.cc Fri Dec 31 03:18:44 2010
@@ -28,6 +28,8 @@
using namespace isc::dns;
using namespace isc::datasrc;
using namespace isc::auth;
+
+namespace {
RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
RRClass::IN(), RRType::A(),
@@ -44,7 +46,9 @@
RRsetPtr noglue_a_rrset(RRsetPtr(new RRset(Name("noglue.example.com"),
RRClass::IN(), RRType::A(),
RRTTL(3600))));
-namespace {
+RRsetPtr soa_rrset = RRsetPtr(new RRset(Name("example.com"),
+ RRClass::IN(), RRType::SOA(),
+ RRTTL(3600)));
// This is a mock Zone class for testing.
// It is a derived class of Zone, and simply hardcode the results of find()
// return SUCCESS for "www.example.com",
@@ -54,14 +58,15 @@
// otherwise return DNAME
class MockZone : public Zone {
public:
- MockZone() :
+ MockZone(bool has_SOA = true) :
origin_(Name("example.com")),
+ has_SOA_(has_SOA),
delegation_rrset(RRsetPtr(new RRset(Name("delegation.example.com"),
- RRClass::IN(), RRType::NS(),
- RRTTL(3600)))),
+ RRClass::IN(), RRType::NS(),
+ RRTTL(3600)))),
cname_rrset(RRsetPtr(new RRset(Name("cname.example.com"),
- RRClass::IN(), RRType::CNAME(),
- RRTTL(3600))))
+ RRClass::IN(), RRType::CNAME(),
+ RRTTL(3600))))
{
delegation_rrset->addRdata(rdata::generic::NS(
Name("glue.ns.example.com")));
@@ -78,10 +83,12 @@
virtual const isc::dns::RRClass& getClass() const;
FindResult find(const isc::dns::Name& name,
- const isc::dns::RRType& type) const;
+ const isc::dns::RRType& type,
+ const FindOptions options = FIND_DEFAULT) const;
private:
Name origin_;
+ bool has_SOA_;
RRsetPtr delegation_rrset;
RRsetPtr cname_rrset;
};
@@ -97,7 +104,7 @@
}
Zone::FindResult
-MockZone::find(const Name& name, const RRType& type) const {
+MockZone::find(const Name& name, const RRType& type, const FindOptions) const {
// hardcode the find results
if (name == Name("www.example.com")) {
return (FindResult(SUCCESS, a_rrset));
@@ -107,6 +114,10 @@
return (FindResult(SUCCESS, noglue_a_rrset));
} else if (name == Name("glue.ns.example.com") && type == RRType::AAAA()) {
return (FindResult(SUCCESS, glue_aaaa_rrset));
+ } else if (name == Name("example.com") && type == RRType::SOA() &&
+ has_SOA_)
+ {
+ return (FindResult(SUCCESS, soa_rrset));
} else if (name == Name("delegation.example.com")) {
return (FindResult(DELEGATION, delegation_rrset));
} else if (name == Name("ns.example.com")) {
@@ -114,7 +125,7 @@
} else if (name == Name("nxdomain.example.com")) {
return (FindResult(NXDOMAIN, RRsetPtr()));
} else if (name == Name("nxrrset.example.com")) {
- return FindResult(NXRRSET, RRsetPtr());
+ return (FindResult(NXRRSET, RRsetPtr()));
} else if ((name == Name("cname.example.com"))) {
return (FindResult(CNAME, cname_rrset));
} else {
@@ -147,7 +158,7 @@
}
TEST_F(QueryTest, matchZone) {
- // match qname, normal query
+ // add a matching zone.
memory_datasrc.addZone(ZonePtr(new MockZone()));
query.process();
EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
@@ -190,12 +201,39 @@
Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
nxdomain_query.process();
EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
+ EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
+ EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+ Name("example.com"), RRClass::IN(), RRType::SOA()));
// NXRRSET
const Name nxrrset_name(Name("nxrrset.example.com"));
Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
nxrrset_query.process();
EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+ EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
+ EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+ Name("example.com"), RRClass::IN(), RRType::SOA()));
+}
+
+/*
+ * This tests that when there's no SOA and we need a negative answer. It should
+ * throw in that case.
+ */
+TEST_F(QueryTest, noSOA) {
+ memory_datasrc.addZone(ZonePtr(new MockZone(false)));
+
+ // The NX Domain
+ const Name nxdomain_name(Name("nxdomain.example.com"));
+ Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
+ EXPECT_THROW(nxdomain_query.process(), Query::NoSOA);
+ // Of course, we don't look into the response, as it throwed
+
+ // NXRRSET
+ const Name nxrrset_name(Name("nxrrset.example.com"));
+ Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
+ EXPECT_THROW(nxrrset_query.process(), Query::NoSOA);
}
TEST_F(QueryTest, noMatchZone) {
@@ -207,4 +245,5 @@
nomatch_query.process();
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
-}
+
+}
Modified: branches/trac453/src/lib/datasrc/memory_datasrc.cc
==============================================================================
--- branches/trac453/src/lib/datasrc/memory_datasrc.cc (original)
+++ branches/trac453/src/lib/datasrc/memory_datasrc.cc Fri Dec 31 03:18:44 2010
@@ -106,6 +106,15 @@
// Try inserting the rrset there
if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
// Ok, we just put it in
+
+ // If this RRset creates a zone cut at this node, mark the node
+ // indicating the need for callback in find().
+ // TBD: handle DNAME, too
+ if (rrset->getType() == RRType::NS() &&
+ rrset->getName() != origin_) {
+ node->enableCallback();
+ }
+
return (result::SUCCESS);
} else {
// The RRSet of given type was already there
@@ -129,16 +138,68 @@
}
}
+ // Maintain intermediate data specific to the search context used in
+ /// \c find().
+ ///
+ /// It will be passed to \c zonecutCallback() and record a possible
+ /// zone cut node and related RRset (normally NS or DNAME).
+ struct FindState {
+ FindState(FindOptions options) : zonecut_node_(NULL),
+ options_(options)
+ {}
+ const DomainNode* zonecut_node_;
+ ConstRRsetPtr rrset_;
+ const FindOptions options_;
+ };
+
+ // A callback called from possible zone cut nodes. This will be passed
+ // from the \c find() method to \c RBTree::find().
+ static bool zonecutCallback(const DomainNode& node, FindState* state) {
+ // We perform callback check only for the highest zone cut in the
+ // rare case of nested zone cuts.
+ if (state->zonecut_node_ != NULL) {
+ return (false);
+ }
+
+ const Domain::const_iterator found(node.getData()->find(RRType::NS()));
+ if (found != node.getData()->end()) {
+ // BIND 9 checks if this node is not the origin. But it cannot
+ // be the origin because we don't enable the callback at the
+ // origin node (see MemoryZoneImpl::add()). Or should we do a
+ // double check for it?
+ state->zonecut_node_ = &node;
+ state->rrset_ = found->second;
+
+ // Unless glue is allowed the search stops here, so we return
+ // false; otherwise return true to continue the search.
+ return ((state->options_ & FIND_GLUE_OK) == 0);
+ }
+
+ // This case should not happen because we enable callback only
+ // when we add an RR searched for above.
+ assert(0);
+ // This is here to avoid warning (therefore compilation error)
+ // in case assert is turned off. Otherwise we could get "Control
+ // reached end of non-void function".
+ return (false);
+ }
+
// Implementation of MemoryZone::find
- FindResult find(const Name& name, RRType type) const {
+ FindResult find(const Name& name, RRType type,
+ const FindOptions options) const
+ {
// Get the node
- DomainNode* node;
- switch (domains_.find(name, &node)) {
+ DomainNode* node(NULL);
+ FindState state(options);
+ switch (domains_.find(name, &node, zonecutCallback, &state)) {
case DomainTree::PARTIALMATCH:
- // Pretend it was not found for now
- // TODO: Implement real delegation. Currently, not having
- // the the domain can cause a partialmatch as well, so
- // better check.
+ if (state.zonecut_node_ != NULL) {
+ return (FindResult(DELEGATION, state.rrset_));
+ }
+ // TODO: we should also cover empty non-terminal cases, which
+ // will require non trivial code and is deferred for later
+ // development. For now, we regard any partial match that
+ // didn't hit a zone cut as "not found".
case DomainTree::NOTFOUND:
return (FindResult(NXDOMAIN, ConstRRsetPtr()));
case DomainTree::EXACTMATCH: // This one is OK, handle it
@@ -149,7 +210,18 @@
assert(node);
assert(!node->isEmpty());
- Domain::const_iterator found(node->getData()->find(type));
+ Domain::const_iterator found;
+
+ // If the node callback is enabled, this may be a zone cut. If it
+ // has a NS RR, we should return a delegation.
+ if (node->isCallbackEnabled()) {
+ found = node->getData()->find(RRType::NS());
+ if (found != node->getData()->end()) {
+ return (FindResult(DELEGATION, found->second));
+ }
+ }
+
+ found = node->getData()->find(type);
if (found != node->getData()->end()) {
// Good, it is here
return (FindResult(SUCCESS, found->second));
@@ -184,8 +256,10 @@
}
Zone::FindResult
-MemoryZone::find(const Name& name, const RRType& type) const {
- return (impl_->find(name, type));
+MemoryZone::find(const Name& name, const RRType& type,
+ const FindOptions options) const
+{
+ return (impl_->find(name, type, options));
}
result::Result
Modified: branches/trac453/src/lib/datasrc/memory_datasrc.h
==============================================================================
--- branches/trac453/src/lib/datasrc/memory_datasrc.h (original)
+++ branches/trac453/src/lib/datasrc/memory_datasrc.h Fri Dec 31 03:18:44 2010
@@ -62,7 +62,8 @@
/// It returns NULL pointer in case of NXDOMAIN and NXRRSET
/// (the base class documentation does not seem to require that).
virtual FindResult find(const isc::dns::Name& name,
- const isc::dns::RRType& type) const;
+ const isc::dns::RRType& type,
+ const FindOptions options = FIND_DEFAULT) const;
/// \brief Inserts an rrset into the zone.
///
Modified: branches/trac453/src/lib/datasrc/rbtree.h
==============================================================================
--- branches/trac453/src/lib/datasrc/rbtree.h (original)
+++ branches/trac453/src/lib/datasrc/rbtree.h Fri Dec 31 03:18:44 2010
@@ -38,8 +38,11 @@
/// Helper function to remove the base domain from super domain
///
/// the precondition of this function is the super_name contains the
-/// sub_name so \code Name a("a.b.c"); Name b("b.c");
-/// Name c = a - b; \\c will be "a" \endcode
+/// sub_name so
+/// \code Name a("a.b.c");
+/// Name b("b.c");
+/// Name c = a - b;
+/// \endcode
///
/// \note function in this namespace is not intended to be used outside.
inline isc::dns::Name
@@ -51,8 +54,9 @@
template <typename T>
class RBTree;
+
/// \brief \c RBNode use by RBTree to store any data related to one domain name
-
+///
/// It has two roles, the first one is as one node in the \c RBTree,
/// the second one is to store the data related to one domain name and maintain
/// the domain name hierarchy struct in one domain name space.
@@ -74,11 +78,11 @@
friend class RBTree<T>;
typedef boost::shared_ptr<T> NodeDataPtr;
- /// \name Deonstructor
+ /// \name Destructor
/// \note it's seems a little strange that constructor is private
/// but deconstructor left public, the reason is for some smart pointer
/// like std::auto_ptr, they needs to delete RBNode in sometimes, but
- /// \code delete *pointer_to_node \codeend shouldn't be called directly
+ /// \code delete *pointer_to_node \endcode shouldn't be called directly
//@{
~RBNode();
//@}
@@ -111,6 +115,24 @@
void setData(const NodeDataPtr& data) { data_ = data; }
//@}
+ /// \name Callback related methods
+ ///
+ /// See the description of \c RBTree<T>::find() about callbacks.
+ ///
+ /// These methods never throw an exception.
+ //@{
+ /// Return if callback is enabled at the node.
+ ///
+ /// This method never throws an exception.
+ bool isCallbackEnabled() const { return (callback_required_); }
+
+ /// Enable callback at the node.
+ void enableCallback() { callback_required_ = true; }
+
+ /// Disable callback at the node.
+ void disableCallback() { callback_required_ = false; }
+ //@}
+
private:
/// \brief Define rbnode color
@@ -147,6 +169,7 @@
isc::dns::Name name_;
NodeDataPtr data_;
+
/// the down pointer points to the root node of sub domains of current
/// domain
/// \par Adding down pointer to \c RBNode is for two purpose:
@@ -154,6 +177,10 @@
/// big flat tree into several hierarchy trees
/// \li It save memory useage, so same label won't be saved several times
RBNode<T>* down_;
+
+ // If true, callback should be called at this node in search.
+ // (This may have to become part of more general "attribute flags")
+ bool callback_required_;
};
@@ -167,7 +194,8 @@
color_(BLACK),
// dummy name, the value doesn't matter:
name_(isc::dns::Name::ROOT_NAME()),
- down_(this)
+ down_(this),
+ callback_required_(false)
{
}
@@ -178,7 +206,8 @@
right_(NULL_NODE()),
color_(RED),
name_(name),
- down_(NULL_NODE())
+ down_(NULL_NODE()),
+ callback_required_(false)
{
}
@@ -186,51 +215,55 @@
template <typename T>
RBNode<T>::~RBNode() {
}
-/// \brief \c RBTree class represents all the domains with the same suffix,
-/// so it can be used to store the domains in one zone.
-///
-/// \c RBTree is a generic red black tree, and contains all the nodes with
-/// the same suffix, since each name may have sub domain names
-/// so \c RBTree is a recursive data structure namely tree in tree.
-/// So for one zone, several RBTrees may be involved. But from outside, the sub
-/// tree is opaque for end users.
-///
-/// \c RBTree split the domain space into hierarchy red black trees, nodes in one
-/// tree has the same base name. The benefit of this struct is that:
-/// - enhance the query performace compared with one big flat red black tree
-/// - decrase the memory footprint to save common labels only once.
-
-/*
-/// \verbatim
-/// with the following names:
-/// a x.d.e.f o.w.y.d.e.f
-/// b z.d.e.f p.w.y.d.e.f
-/// c g.h q.w.y.d.e.f
-/// the tree will looks like:
-/// b
-/// / \
-/// a d.e.f
-/// /|\
-/// c | g.h
-/// |
-/// w.y
-/// /|\
-/// x | z
-/// |
-/// p
-/// / \
-/// o q
-/// \endverbatim
-/// \note open problems:
-/// - current find funciton only return non-empty nodes, so there is no difference
-/// between find one not exist name with empty non-terminal nodes, but in DNS query
-/// logic, they are different
-/// \todo
-/// - add remove interface
-/// - add iterator to iterate the whole rbtree while may needed by axfr
-/// - since \c RBNode only has down pointer without up pointer, the node path during finding
-/// should be recorded for later use
-*/
+
+// note: the following class description is documented using C-style comments
+// because the verbatim diagram contain a backslash, which could be interpreted
+// as part of a multi-line comment with C++ style comments.
+/**
+ * \brief \c RBTree class represents all the domains with the same suffix,
+ * so it can be used to store the domains in one zone.
+ *
+ * \c RBTree is a generic red black tree, and contains all the nodes with
+ * the same suffix, since each name may have sub domain names
+ * so \c RBTree is a recursive data structure namely tree in tree.
+ * So for one zone, several RBTrees may be involved. But from outside, the sub
+ * tree is opaque for end users.
+ *
+ * \c RBTree split the domain space into hierarchy red black trees, nodes in one
+ * tree has the same base name. The benefit of this struct is that:
+ * - enhance the query performace compared with one big flat red black tree
+ * - decrase the memory footprint to save common labels only once.
+ *
+ * \verbatim
+ with the following names:
+ a x.d.e.f o.w.y.d.e.f
+ b z.d.e.f p.w.y.d.e.f
+ c g.h q.w.y.d.e.f
+ the tree will looks like:
+ b
+ / \
+ a d.e.f
+ /|\
+ c | g.h
+ |
+ w.y
+ /|\
+ x | z
+ |
+ p
+ / \
+ o q
+ * \endverbatim
+ * \note open problems:
+ * - current find funciton only return non-empty nodes, so there is no difference
+ * between find one not exist name with empty non-terminal nodes, but in DNS query
+ * logic, they are different
+ * \todo
+ * - add remove interface
+ * - add iterator to iterate the whole rbtree while may needed by axfr
+ * - since \c RBNode only has down pointer without up pointer, the node path during finding
+ * should be recorded for later use
+ */
template <typename T>
class RBTree : public boost::noncopyable {
friend class RBNode<T>;
@@ -247,7 +280,7 @@
/// \name Constructor and Destructor
//@{
- RBTree();
+ explicit RBTree();
/// \b Note: RBTree is not intended to be inherited so the destructor
/// is not virtual
@@ -256,13 +289,98 @@
/// \name Inquery methods
//@{
- /// \brief Find the node with the name
+ /// \brief Find the node that gives a longest match against the given name
+ ///
+ /// This method searches the \c RBTree for a node whose name is a longest
+ /// match against \c name. The found node, if any, is returned via the
+ /// \c node pointer.
+ /// By default, nodes that don't have data will be ignored, and the result
+ /// can be \c NOTFOUND even if there is a node whose name matches the
+ /// given \c name.
+ /// We'll soon introduce a "no data OK" mode in this method. It would
+ /// match any node of the tree regardless of whether the node has data
+ /// or not.
+ /// Since the tree is "compressed", i.e., a node can contain multiple
+ /// name labels, there are counter intuitive cases in the "no data OK"
+ /// mode. For example, see the diagram of the class description.
+ /// Name "y.d.e.f" is logically contained in the tree as part of the
+ /// "compressed" node of "w.y". But the search logic of this method
+ /// cannot find the logical match, and would return a \c PARTIALMATCH
+ /// result pointing to node "d.e.f". To correctly identify the real
+ /// longest match, "y.d.e.f" with empty data, the caller needs to
+ /// perform additional steps.
+ ///
+ /// This version of \c find() method is templated to allow the caller
+ /// to specify a "hook" at nodes that give a partial match.
+ /// When the search encounters a node with data that partially matches
+ /// \c name (i.e. node's name is a superdomain of \c name) and has
+ /// enabled callback (via the \c RBNode::enableCallback() method), if
+ /// \c callback is non \c NULL then the callback function is called
+ /// with the argument of a reference to the node and the given
+ /// callback argument (\c callback_arg). The template parameter specifies
+ /// the type of the callback argument.
+ /// The callback function returns either \c true or \c false, meaning
+ /// the search should stop or continue, respectively.
+ /// If the return value is \c true the search stops immediately at the
+ /// node even if there could be a longer matching name below it.
+ /// In reality, this convoluted callback rule is specifically intended
+ /// to be used to handle a zone cut (delegation) at a name search inside
+ /// a zone, and won't be used in any other cases.
+ /// Other applications of the tree won't need callbacks, and they should
+ /// use the non templated version of the \c find() method.
+ ///
+ /// Since the expected usage of callback is very limited, we do not
+ /// generalize the interface so that it can be an arbitrary functions or
+ /// functor objects in favor of simplicity and efficiency.
+ ///
+ /// This method involves operations on names that can throw an exception.
+ /// If that happens the exception will be propagated to the caller.
+ /// The callback function should generally not throw an exception, but
+ /// if it throws, the exception will be propagated to the caller.
+ ///
/// \param name Target to be found
- /// \param node Point to the node when the return vaule is \c not
- /// NOTFOUND, if the return value is NOTFOUND, the value of node is
- /// \c unknown
- Result find(const isc::dns::Name& name, RBNode<T>** node) const;
- Result find(const isc::dns::Name& name, const RBNode<T>** node) const;
+ /// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
+ /// it will store a pointer to the matching node
+ /// \param callback If non \c NULL, a call back function to be called
+ /// at "delegation" nodes (see above).
+ /// \param callback_arg A caller supplied argument to be passed to
+ /// \c callback.
+ ///
+ /// \return \c EXACTMATCH A node that whose name is equal to \c name is
+ /// found. \c *node will be set to point to that node.
+ /// \return \c PARTIALMATCH There is a no exact match, but a superdomain
+ /// of \c name exists. \c node will be set to point to the node whose
+ /// name is the longest among such superdomains.
+ /// \return \c NOTFOUND There is no exact or partial match against \c name
+ /// \c *node will be intact in this case.
+ template <typename CBARG>
+ Result find(const isc::dns::Name& name, RBNode<T>** node,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const;
+
+ /// Same as the other version, but the returned \c node will be immutable.
+ template <typename CBARG>
+ Result find(const isc::dns::Name& name, const RBNode<T>** node,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const;
+
+ /// Same as the templated version, but does not use callback.
+ ///
+ /// Applications except the zone implementation should generally use the
+ /// non templated version.
+ Result find(const isc::dns::Name& name, RBNode<T>** node) const {
+ return (find<void*>(name, node, NULL, NULL));
+ }
+
+ /// Same as the templated version, but does not use callback, and the
+ /// returned \c node will be immutable.
+ ///
+ /// In general, this version should be preferred over the other non
+ /// templated version, unless the caller knows it should modify the
+ /// returned node.
+ Result find(const isc::dns::Name& name, const RBNode<T>** node) const {
+ return (find<void*>(name, node, NULL, NULL));
+ }
/// \brief Get the total node count in the tree
/// the node count including the node created common suffix node,
@@ -289,8 +407,8 @@
/// - ALREADYEXIST means already has the node with the given name
//
/// \node To modify the data related with one name but not sure the name has
- /// inserted or not, it is better to call \code insert \endcode,instead of
- /// \code find() \endcode, in case the name isn't exist and needs to insert again
+ /// inserted or not, it is better to call \c insert,instead of
+ /// \c find(), in case the name isn't exist and needs to insert again
Result insert(const isc::dns::Name& name, RBNode<T>** inserted_node);
//@}
@@ -325,8 +443,11 @@
/// and node will points to c.b.a
/// \note parameter up now is not used by any funciton, but we are gonna
/// need it soon to implement function like remove
+ template <typename CBARG>
Result findHelper(const isc::dns::Name& name, const RBNode<T>** up,
- RBNode<T>** node) const;
+ RBNode<T>** node,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const;
void dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
unsigned int depth) const;
/// for indent purpose, add certian mount empty charachter to output stream
@@ -390,30 +511,39 @@
--node_count_;
}
-template <typename T>
+template <typename T> template <typename CBARG>
typename RBTree<T>::Result
-RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node) const {
+RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const
+{
const RBNode<T>* up_node = NULLNODE;
- return (findHelper(name, &up_node, node));
-}
-
-template <typename T>
+ return (findHelper(name, &up_node, node, callback, callback_arg));
+}
+
+template <typename T> template <typename CBARG>
typename RBTree<T>::Result
-RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node) const {
+RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const
+{
const RBNode<T>* up_node;
RBNode<T>* target_node;
const typename RBTree<T>::Result ret =
- findHelper(name, &up_node, &target_node);
+ findHelper(name, &up_node, &target_node, callback, callback_arg);
if (ret != NOTFOUND) {
*node = target_node;
}
return (ret);
}
-template <typename T>
+template <typename T> template <typename CBARG>
typename RBTree<T>::Result
-RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_node,
- RBNode<T>** target) const
+RBTree<T>::findHelper(const isc::dns::Name& target_name,
+ const RBNode<T>** up_node,
+ RBNode<T>** target,
+ bool (*callback)(const RBNode<T>&, CBARG),
+ CBARG callback_arg) const
{
using namespace helper;
@@ -441,12 +571,17 @@
node = (compare_result.getOrder() < 0) ?
node->left_ : node->right_;
} else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
- *up_node = node;
- name = name - node->name_;
if (!node->isEmpty()) {
ret = RBTree<T>::PARTIALMATCH;
*target = node;
+ if (callback != NULL && node->callback_required_) {
+ if ((callback)(*node, callback_arg)) {
+ break;
+ }
+ }
}
+ *up_node = node;
+ name = name - node->name_;
node = node->down_;
} else {
break;
@@ -541,6 +676,7 @@
// after the RBNode creation
std::auto_ptr<RBNode<T> > down_node(new RBNode<T>(sub_name));
std::swap(node.data_, down_node->data_);
+ std::swap(node.callback_required_, down_node->callback_required_);
down_node->down_ = node.down_;
node.name_ = base_name;
node.down_ = down_node.get();
Modified: branches/trac453/src/lib/datasrc/tests/memory_datasrc_unittest.cc
==============================================================================
--- branches/trac453/src/lib/datasrc/tests/memory_datasrc_unittest.cc (original)
+++ branches/trac453/src/lib/datasrc/tests/memory_datasrc_unittest.cc Fri Dec 31 03:18:44 2010
@@ -27,6 +27,9 @@
using namespace isc::datasrc;
namespace {
+// Commonly used result codes (Who should write the prefix all the time)
+using result::SUCCESS;
+using result::EXIST;
class MemoryDataSrcTest : public ::testing::Test {
protected:
@@ -139,18 +142,31 @@
class_(RRClass::IN()),
origin_("example.org"),
ns_name_("ns.example.org"),
+ child_ns_name_("child.example.org"),
+ child_glue_name_("ns.child.example.org"),
+ grandchild_ns_name_("grand.child.example.org"),
+ grandchild_glue_name_("ns.grand.child.example.org"),
zone_(class_, origin_),
rr_out_(new RRset(Name("example.com"), class_, RRType::A(),
RRTTL(300))),
rr_ns_(new RRset(origin_, class_, RRType::NS(), RRTTL(300))),
rr_ns_a_(new RRset(ns_name_, class_, RRType::A(), RRTTL(300))),
rr_ns_aaaa_(new RRset(ns_name_, class_, RRType::AAAA(), RRTTL(300))),
- rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300)))
+ rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300))),
+ rr_child_ns_(new RRset(child_ns_name_, class_, RRType::NS(),
+ RRTTL(300))),
+ rr_child_glue_(new RRset(child_glue_name_, class_, RRType::A(),
+ RRTTL(300))),
+ rr_grandchild_ns_(new RRset(grandchild_ns_name_, class_, RRType::NS(),
+ RRTTL(300))),
+ rr_grandchild_glue_(new RRset(grandchild_glue_name_, class_,
+ RRType::AAAA(), RRTTL(300)))
{
}
// Some data to test with
- RRClass class_;
- Name origin_, ns_name_;
+ const RRClass class_;
+ const Name origin_, ns_name_, child_ns_name_, child_glue_name_,
+ grandchild_ns_name_, grandchild_glue_name_;
// The zone to torture by tests
MemoryZone zone_;
@@ -160,7 +176,7 @@
* inside anyway. We will check it finds them and does not change
* the pointer.
*/
- RRsetPtr
+ ConstRRsetPtr
// Out of zone RRset
rr_out_,
// NS of example.org
@@ -171,6 +187,10 @@
rr_ns_aaaa_,
// A of example.org
rr_a_;
+ ConstRRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
+ ConstRRsetPtr rr_child_glue_; // glue RR of the child domain
+ ConstRRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
+ ConstRRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
/**
* \brief Test one find query to the zone.
@@ -189,8 +209,10 @@
* uses zone_)
*/
void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
- bool check_answer = true,
- const ConstRRsetPtr& answer = ConstRRsetPtr(), MemoryZone *zone = NULL)
+ bool check_answer = true,
+ const ConstRRsetPtr& answer = ConstRRsetPtr(),
+ MemoryZone *zone = NULL,
+ Zone::FindOptions options = Zone::FIND_DEFAULT)
{
if (!zone) {
zone = &zone_;
@@ -198,13 +220,14 @@
// The whole block is inside, because we need to check the result and
// we can't assign to FindResult
EXPECT_NO_THROW({
- Zone::FindResult find_result(zone->find(name, rrtype));
- // Check it returns correct answers
- EXPECT_EQ(result, find_result.code);
- if (check_answer) {
- EXPECT_EQ(answer, find_result.rrset);
- }
- });
+ Zone::FindResult find_result(zone->find(name, rrtype,
+ options));
+ // Check it returns correct answers
+ EXPECT_EQ(result, find_result.code);
+ if (check_answer) {
+ EXPECT_EQ(answer, find_result.rrset);
+ }
+ });
}
};
@@ -230,7 +253,6 @@
// Test null pointer
EXPECT_THROW(zone_.add(ConstRRsetPtr()), MemoryZone::NullRRset);
- using namespace result; // Who should write the prefix all the time
// Now put all the data we have there. It should throw nothing
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
@@ -240,6 +262,89 @@
// Try putting there something twice, it should be rejected
EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_)));
EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_a_)));
+}
+
+// Test adding child zones and zone cut handling
+TEST_F(MemoryZoneTest, delegationNS) {
+ // add in-zone data
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+
+ // install a zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+
+ // below the zone cut
+ findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_);
+
+ // at the zone cut
+ findTest(Name("child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_);
+ findTest(Name("child.example.org"), RRType::NS(), Zone::DELEGATION,
+ true, rr_child_ns_);
+
+ // finding NS for the apex (origin) node. This must not be confused
+ // with delegation due to the existence of an NS RR.
+ findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+
+ // unusual case of "nested delegation": the highest cut should be used.
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
+ findTest(Name("www.grand.child.example.org"), RRType::A(),
+ Zone::DELEGATION, true, rr_child_ns_); // note: !rr_grandchild_ns_
+}
+
+TEST_F(MemoryZoneTest, glue) {
+ // install zone data:
+ // a zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+ // glue for this cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));
+ // a nested zone cut (unusual)
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
+ // glue under the deeper zone cut
+ EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_glue_)));
+
+ // by default glue is hidden due to the zone cut
+ findTest(child_glue_name_, RRType::A(), Zone::DELEGATION, true,
+ rr_child_ns_);
+
+
+ // If we do it in the "glue OK" mode, we should find the exact match.
+ findTest(child_glue_name_, RRType::A(), Zone::SUCCESS, true,
+ rr_child_glue_, NULL, Zone::FIND_GLUE_OK);
+
+ // glue OK + NXRRSET case
+ findTest(child_glue_name_, RRType::AAAA(), Zone::NXRRSET, true,
+ ConstRRsetPtr(), NULL, Zone::FIND_GLUE_OK);
+
+ // glue OK + NXDOMAIN case
+ findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
+ true, rr_child_ns_, NULL, Zone::FIND_GLUE_OK);
+
+ // TODO:
+ // glue name would match a wildcard under a zone cut: wildcard match
+ // shouldn't happen under a cut and result must be PARTIALMATCH
+ // (This case cannot be tested yet)
+
+ // nested cut case. The glue should be found.
+ findTest(grandchild_glue_name_, RRType::AAAA(), Zone::SUCCESS,
+ true, rr_grandchild_glue_, NULL, Zone::FIND_GLUE_OK);
+
+ // A non-existent name in nested cut. This should result in delegation
+ // at the highest zone cut.
+ findTest(Name("www.grand.child.example.org"), RRType::TXT(),
+ Zone::DELEGATION, true, rr_child_ns_, NULL, Zone::FIND_GLUE_OK);
+}
+
+// Test adding DNAMEs and resulting delegation handling
+// Listing ideas only for now
+TEST_F(MemoryZoneTest, delegationDNAME) {
+ // apex DNAME: allowed by spec. No DNAME delegation at the apex;
+ // descendants are subject to delegation.
+
+ // Other cases of NS and DNAME mixture are prohibited.
+ // BIND 9 doesn't reject such cases at load time, however.
+
+ // DNAME and ordinary types (allowed by spec)
}
/**
@@ -251,7 +356,6 @@
*/
TEST_F(MemoryZoneTest, find) {
// Fill some data inside
- using namespace result; // Who should write the prefix all the time
// Now put all the data we have there. It should throw nothing
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
Modified: branches/trac453/src/lib/datasrc/tests/rbtree_unittest.cc
==============================================================================
--- branches/trac453/src/lib/datasrc/tests/rbtree_unittest.cc (original)
+++ branches/trac453/src/lib/datasrc/tests/rbtree_unittest.cc Fri Dec 31 03:18:44 2010
@@ -169,6 +169,61 @@
EXPECT_EQ(Name("q"), rbtnode->getName());
}
+bool
+testCallback(const RBNode<int>&, bool* callack_checker) {
+ *callack_checker = true;
+ return (false);
+}
+
+TEST_F(RBTreeTest, callback) {
+ // by default callback isn't enabled
+ EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("callback.example"),
+ &rbtnode));
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
+ EXPECT_FALSE(rbtnode->isCallbackEnabled());
+
+ // enable/re-disable callback
+ rbtnode->enableCallback();
+ EXPECT_TRUE(rbtnode->isCallbackEnabled());
+ rbtnode->disableCallback();
+ EXPECT_FALSE(rbtnode->isCallbackEnabled());
+
+ // enable again for subsequent tests
+ rbtnode->enableCallback();
+ // add more levels below and above the callback node for partial match.
+ RBNode<int>* subrbtnode;
+ EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("sub.callback.example"),
+ &subrbtnode));
+ subrbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
+ RBNode<int>* parentrbtnode;
+ EXPECT_EQ(RBTree<int>::ALREADYEXIST, rbtree.insert(Name("example"),
+ &parentrbtnode));
+ // the chilld/parent nodes shouldn't "inherit" the callback flag.
+ // "rbtnode" may be invalid due to the insertion, so we need to re-find
+ // it.
+ EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("callback.example"),
+ &rbtnode));
+ EXPECT_TRUE(rbtnode->isCallbackEnabled());
+ EXPECT_FALSE(subrbtnode->isCallbackEnabled());
+ EXPECT_FALSE(parentrbtnode->isCallbackEnabled());
+
+ // check if the callback is called from find()
+ bool callback_called = false;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree.find(Name("sub.callback.example"), &crbtnode,
+ testCallback, &callback_called));
+ EXPECT_TRUE(callback_called);
+
+ // enable callback at the parent node, but it doesn't have data so
+ // the callback shouldn't be called.
+ parentrbtnode->enableCallback();
+ callback_called = false;
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ rbtree.find(Name("callback.example"), &crbtnode,
+ testCallback, &callback_called));
+ EXPECT_FALSE(callback_called);
+}
+
TEST_F(RBTreeTest, dumpTree) {
std::ostringstream str;
std::ostringstream str2;
Modified: branches/trac453/src/lib/datasrc/zone.h
==============================================================================
--- branches/trac453/src/lib/datasrc/zone.h (original)
+++ branches/trac453/src/lib/datasrc/zone.h Fri Dec 31 03:18:44 2010
@@ -98,6 +98,16 @@
{}
const Result code;
const isc::dns::ConstRRsetPtr rrset;
+ };
+
+ /// Find options.
+ ///
+ /// The option values are used as a parameter for \c find().
+ /// These are values of a bitmask type. Bitwise operations can be
+ /// performed on these values to express compound options.
+ enum FindOptions {
+ FIND_DEFAULT = 0, ///< The default options
+ FIND_GLUE_OK = 1 ///< Allow search under a zone cut
};
///
@@ -150,6 +160,17 @@
/// - If the search name matches a delegation point of DNAME, it returns
/// the code of \c DNAME and that DNAME RR.
///
+ /// The \c options parameter specifies customized behavior of the search.
+ /// Their semantics is as follows:
+ /// - \c GLUE_OK Allow search under a zone cut. By default the search
+ /// will stop once it encounters a zone cut. If this option is specified
+ /// it remembers information about the highest zone cut and continues
+ /// the search until it finds an exact match for the given name or it
+ /// detects there is no exact match. If an exact match is found,
+ /// RRsets for that name are searched just like the normal case;
+ /// otherwise, if the search has encountered a zone cut, \c DELEGATION
+ /// with the information of the highest zone cut will be returned.
+ ///
/// A derived version of this method may involve internal resource
/// allocation, especially for constructing the resulting RRset, and may
/// throw an exception if it fails.
@@ -162,9 +183,12 @@
///
/// \param name The domain name to be searched for.
/// \param type The RR type to be searched for.
+ /// \param options The search options.
/// \return A \c FindResult object enclosing the search result (see above).
virtual FindResult find(const isc::dns::Name& name,
- const isc::dns::RRType& type) const = 0;
+ const isc::dns::RRType& type,
+ const FindOptions options
+ = FIND_DEFAULT) const = 0;
//@}
};
@@ -177,4 +201,8 @@
}
}
-#endif
+#endif // __ZONE_H
+
+// Local Variables:
+// mode: c++
+// End:
Modified: branches/trac453/src/lib/datasrc/zonetable.h
==============================================================================
--- branches/trac453/src/lib/datasrc/zonetable.h (original)
+++ branches/trac453/src/lib/datasrc/zonetable.h Fri Dec 31 03:18:44 2010
@@ -25,7 +25,7 @@
namespace dns {
class Name;
class RRClass;
-};
+}
namespace datasrc {
Modified: branches/trac453/src/lib/testutils/testdata/Makefile.am
==============================================================================
--- branches/trac453/src/lib/testutils/testdata/Makefile.am (original)
+++ branches/trac453/src/lib/testutils/testdata/Makefile.am Fri Dec 31 03:18:44 2010
@@ -4,6 +4,7 @@
BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
BUILT_SOURCES += queryBadEDNS_fromWire.wire shortanswer_fromWire.wire
BUILT_SOURCES += simplequery_fromWire.wire simpleresponse_fromWire.wire
+BUILT_SOURCES += iquery_fromWire.wire iquery_response_fromWire.wire
# NOTE: keep this in sync with real file listing
# so is included in tarball
@@ -18,6 +19,7 @@
EXTRA_DIST += shortresponse_fromWire
EXTRA_DIST += simplequery_fromWire.spec
EXTRA_DIST += simpleresponse_fromWire.spec
+EXTRA_DIST += iquery_fromWire.spec iquery_response_fromWire.spec
EXTRA_DIST += example.com.zone example.net.zone example.org.zone example.zone
EXTRA_DIST += example.com
More information about the bind10-changes
mailing list