Parcourir la source

Sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac458@4146 e5f2f494-b856-4b98-b285-d166d9295462
Michal Vaner il y a 14 ans
Parent
commit
8e7af7e1cd
47 fichiers modifiés avec 1882 ajouts et 588 suppressions
  1. 16 0
      ChangeLog
  2. 2 0
      configure.ac
  3. 12 25
      src/bin/auth/auth_srv.cc
  4. 145 43
      src/bin/auth/benchmarks/query_bench.cc
  5. 10 5
      src/bin/auth/config.cc
  6. 69 3
      src/bin/auth/query.cc
  7. 71 2
      src/bin/auth/query.h
  8. 1 0
      src/bin/auth/tests/Makefile.am
  9. 132 10
      src/bin/auth/tests/auth_srv_unittest.cc
  10. 72 22
      src/bin/auth/tests/config_unittest.cc
  11. 123 13
      src/bin/auth/tests/query_unittest.cc
  12. 1 1
      src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec
  13. 1 0
      src/bin/recurse/tests/Makefile.am
  14. 16 2
      src/bin/recurse/tests/recursor_config_unittest.cc
  15. 19 10
      src/bin/recurse/tests/recursor_unittest.cc
  16. 1 1
      src/lib/asiolink/Makefile.am
  17. 1 1
      src/lib/asiolink/asiolink.h
  18. 1 0
      src/lib/asiolink/internal/Makefile.am
  19. 37 0
      src/lib/asiolink/internal/tests/Makefile.am
  20. 21 0
      src/lib/asiolink/internal/tests/run_unittests.cc
  21. 0 0
      src/lib/asiolink/internal/tests/udpdns_unittest.cc
  22. 0 1
      src/lib/asiolink/tests/Makefile.am
  23. 72 40
      src/lib/asiolink/tests/asiolink_unittest.cc
  24. 2 2
      src/lib/datasrc/cache.h
  25. 2 2
      src/lib/datasrc/data_source.cc
  26. 117 13
      src/lib/datasrc/memory_datasrc.cc
  27. 26 1
      src/lib/datasrc/memory_datasrc.h
  28. 221 75
      src/lib/datasrc/rbtree.h
  29. 159 14
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  30. 85 0
      src/lib/datasrc/tests/rbtree_unittest.cc
  31. 4 0
      src/lib/datasrc/tests/testdata/duplicate_rrset.zone
  32. 30 2
      src/lib/datasrc/zone.h
  33. 1 1
      src/lib/datasrc/zonetable.h
  34. 10 9
      src/lib/python/isc/config/module_spec.py
  35. 4 0
      src/lib/python/isc/config/tests/module_spec_test.py
  36. 12 4
      src/lib/testutils/Makefile.am
  37. 0 2
      src/lib/testutils/README
  38. 272 0
      src/lib/testutils/srv_test.cc
  39. 82 126
      src/lib/testutils/srv_test.h
  40. 0 158
      src/lib/testutils/srv_unittest.h
  41. 3 0
      src/lib/testutils/testdata/Makefile.am
  42. 3 0
      src/lib/testutils/testdata/example.com.zone
  43. 3 0
      src/lib/testutils/testdata/example.net.zone
  44. 3 0
      src/lib/testutils/testdata/example.org.zone
  45. 3 0
      src/lib/testutils/testdata/example.zone
  46. 8 0
      src/lib/testutils/testdata/iquery_fromWire.spec
  47. 9 0
      src/lib/testutils/testdata/iquery_response_fromWire.spec

+ 16 - 0
ChangeLog

@@ -1,3 +1,19 @@
+  143.	[build]		jinmei
+	Fixed build problems with clang++ in unit tests due to recent
+	changes.  No behavior change. (Trac #448, svn r4133)
+
+  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

+ 2 - 0
configure.ac

@@ -597,6 +597,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/Makefile
                  src/lib/asiolink/Makefile
                  src/lib/asiolink/tests/Makefile
+                 src/lib/asiolink/internal/Makefile
+                 src/lib/asiolink/internal/tests/Makefile
                  src/lib/bench/Makefile
                  src/lib/bench/example/Makefile
                  src/lib/bench/tests/Makefile

+ 12 - 25
src/bin/auth/auth_srv.cc

@@ -162,33 +162,20 @@ private:
     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

+ 145 - 43
src/bin/auth/benchmarks/query_bench.cc

@@ -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 @@ XfroutClient xfrout_client("dummy_path"); // path doesn't matter
 // 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 @@ public:
         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 @@ public:
             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());
     }
-private:
+protected:
     AuthSrvPtr server_;
+private:
     const BenchQueries& queries_;
     MessagePtr query_message_;
     OutputBufferPtr buffer_;
@@ -108,26 +107,92 @@ private:
     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 @@ usage() {
 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 @@ main(int argc, char* argv[]) {
     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 @@ main(int argc, char* argv[]) {
 
     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));
+    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<QueryBenchMark>(iteration,
-                              QueryBenchMark(10 * queries.size(), datasrc_file,
+        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);
 }

+ 10 - 5
src/bin/auth/config.cc

@@ -151,16 +151,21 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
             isc_throw(AuthConfigError, "Missing zone file for zone: "
                       << origin->str());
         }
-        const result::Result result = memory_datasrc_->addZone(
-            ZonePtr(new MemoryZone(rrclass_, Name(origin->stringValue()))));
+        shared_ptr<MemoryZone> new_zone(new MemoryZone(rrclass_,
+            Name(origin->stringValue())));
+        const result::Result result = memory_datasrc_->addZone(new_zone);
         if (result == result::EXIST) {
             isc_throw(AuthConfigError, "zone "<< origin->str()
                       << " already exists");
         }
 
-        // TODO
-        // then load the zone from 'file', which is currently not implemented.
-        //
+        /*
+         * TODO: Once we have better reloading of configuration (something
+         * else than throwing everything away and loading it again), we will
+         * need the load method to be split into some kind of build and
+         * commit/abort parts.
+         */
+        new_zone->load(file->stringValue());
     }
 }
 

+ 69 - 3
src/bin/auth/query.cc

@@ -25,12 +25,71 @@
 
 using namespace isc::dns;
 using namespace isc::datasrc;
+using namespace isc::dns::rdata;
 using namespace std;
 
 namespace isc {
 namespace auth {
 
 void
+Query::getAdditional(const isc::datasrc::Zone& zone,
+                     const isc::dns::RRset& rrset) const
+{
+    if (rrset.getType() == RRType::NS()) {
+        // Need to perform the search in the "GLUE OK" mode.
+        RdataIteratorPtr rdata_iterator = rrset.getRdataIterator();
+        for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
+             const Rdata& rdata(rdata_iterator->getCurrent());
+             const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
+             findAddrs(zone, ns.getNSName(), Zone::FIND_GLUE_OK);
+        }
+    }
+}
+
+void
+Query::findAddrs(const isc::datasrc::Zone& zone,
+                 const isc::dns::Name& qname,
+                 const isc::datasrc::Zone::FindOptions options) const
+{
+    // Out of zone name
+    NameComparisonResult result = zone.getOrigin().compare(qname);
+    if ((result.getRelation() != NameComparisonResult::SUPERDOMAIN) &&
+        (result.getRelation() != NameComparisonResult::EQUAL))
+        return;
+
+    // Find A rrset
+    Zone::FindResult a_result = zone.find(qname, RRType::A(), options);
+    if (a_result.code == Zone::SUCCESS) {
+        response_.addRRset(Message::SECTION_ADDITIONAL,
+                     boost::const_pointer_cast<RRset>(a_result.rrset));
+    }
+    // Find AAAA rrset
+    Zone::FindResult aaaa_result = zone.find(qname, RRType::AAAA(), options);
+    if (aaaa_result.code == Zone::SUCCESS) {
+        response_.addRRset(Message::SECTION_ADDITIONAL,
+                     boost::const_pointer_cast<RRset>(aaaa_result.rrset));
+    }
+}
+
+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);
@@ -100,15 +159,21 @@ Query::process() const {
                 // TODO : fill in authority and addtional sections.
                 break;
             case Zone::DELEGATION:
-                // TODO : add NS to authority section, fill in additional section.
+                response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
+                response_.setRcode(Rcode::NOERROR());
+                response_.addRRset(Message::SECTION_AUTHORITY,
+                            boost::const_pointer_cast<RRset>(db_result.rrset));
+                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:
@@ -117,5 +182,6 @@ Query::process() const {
         }
     }
 }
+
 }
 }

+ 71 - 2
src/bin/auth/query.h

@@ -14,11 +14,15 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <exceptions/exceptions.h>
+#include <datasrc/zone.h>
+
 namespace isc {
 namespace dns {
 class Message;
 class Name;
 class RRType;
+class RRset;
 }
 
 namespace datasrc {
@@ -60,6 +64,48 @@ namespace auth {
 /// accidentally, and since it's considered a temporary development state,
 /// we keep this name at the moment.
 class Query {
+private:
+
+    /// \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;
+
+    /// Look up additional data (i.e., address records for the names included
+    /// in NS or MX records).
+    ///
+    /// This method may throw a exception because its underlying methods may
+    /// throw exceptions.
+    ///
+    /// \param zone The Zone wherein the additional data to the query is bo be
+    /// found.
+    /// \param rrset The RRset (i.e., NS or MX rrset) which require additional
+    /// processing.
+    void getAdditional(const isc::datasrc::Zone& zone,
+                       const isc::dns::RRset& rrset) const;
+
+    /// Find address records for a specified name.
+    ///
+    /// Search the specified zone for AAAA/A RRs of each of the NS/MX RDATA
+    /// (domain name), and insert the found ones into the additional section
+    /// if address records are available. By default the search will stop
+    /// once it encounters a zone cut.
+    ///
+    /// Note: we need to perform the search in the "GLUE OK" mode for NS RDATA,
+    /// which means that we should include A/AAAA RRs under a zone cut.
+    /// The glue records must exactly match the name in the NS RDATA, without
+    /// CNAME or wildcard processing.
+    ///
+    /// \param zone The Zone wherein the address records is to be found.
+    /// \param qname The name in rrset RDATA.
+    /// \param options The search options.
+    void findAddrs(const isc::datasrc::Zone& zone,
+                   const isc::dns::Name& qname,
+                   const isc::datasrc::Zone::FindOptions options
+                   = isc::datasrc::Zone::FIND_DEFAULT) const;
+
 public:
     /// Constructor from query parameters.
     ///
@@ -100,10 +146,33 @@ public:
     /// 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_;

+ 1 - 0
src/bin/auth/tests/Makefile.am

@@ -33,6 +33,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la

+ 132 - 10
src/bin/auth/tests/auth_srv_unittest.cc

@@ -15,16 +15,34 @@
 // $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>
 #include <auth/statistics.h>
 
+#include <dns/tests/unittest_util.h>
+#include <testutils/srv_test.h>
+
+using namespace std;
 using namespace isc::cc;
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::xfr;
 using namespace asiolink;
+using namespace isc::testutils;
 using isc::UnitTestUtil;
 
 namespace {
@@ -41,56 +59,160 @@ protected:
         server.setXfrinSession(&notify_session);
         server.setStatisticsSession(&statistics_session);
     }
+    virtual void processMessage() {
+        server.processMessage(*io_message, parse_message, response_obuffer,
+                              &dnsserv);
+    }
     MockSession statistics_session;
     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) {
-    UNSUPPORTED_REQUEST_TEST;
+    unsupportedRequest();
 }
 
 // Simple API check
 TEST_F(AuthSrvTest, verbose) {
-    VERBOSE_TEST;
+    EXPECT_FALSE(server.getVerbose());
+    server.setVerbose(true);
+    EXPECT_TRUE(server.getVerbose());
+    server.setVerbose(false);
+    EXPECT_FALSE(server.getVerbose());
 }
 
 // Multiple questions.  Should result in FORMERR.
 TEST_F(AuthSrvTest, multiQuestion) {
-    MULTI_QUESTION_TEST;
+    multiQuestion();
 }
 
 // Incoming data doesn't even contain the complete header.  Must be silently
 // dropped.
 TEST_F(AuthSrvTest, shortMessage) {
-    SHORT_MESSAGE_TEST;
+    shortMessage();
 }
 
 // Response messages.  Must be silently dropped, whether it's a valid response
 // or malformed or could otherwise cause a protocol error.
 TEST_F(AuthSrvTest, response) {
-    RESPONSE_TEST;
+    response();
 }
 
 // Query with a broken question
 TEST_F(AuthSrvTest, shortQuestion) {
-    SHORT_QUESTION_TEST;
+    shortQuestion();
 }
 
 // Query with a broken answer section
 TEST_F(AuthSrvTest, shortAnswer) {
-    SHORT_ANSWER_TEST;
+    shortAnswer();
 }
 
 // Query with unsupported version of EDNS.
 TEST_F(AuthSrvTest, ednsBadVers) {
-    EDNS_BADVERS_TEST;
+    ednsBadVers();
 }
 
 TEST_F(AuthSrvTest, AXFROverUDP) {
-    AXFR_OVER_UDP_TEST;
+    axfrOverUDP();
 }
 
 TEST_F(AuthSrvTest, AXFRSuccess) {

+ 72 - 22
src/bin/auth/tests/config_unittest.cc

@@ -17,6 +17,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/rrclass.h>
+#include <dns/masterload.h>
 
 #include <cc/data.h>
 
@@ -143,33 +144,42 @@ TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
 }
 
 TEST_F(MemoryDatasrcConfigTest, addOneZone) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+    // Check it actually loaded something
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(rrclass)->findZone(
+        Name("ns.example.com.")).zone->find(Name("ns.example.com."),
+        RRType::A()).code);
 }
 
 TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"},"
                       "              {\"origin\": \"example.org\","
-                      "               \"file\": \"example.org.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
                       "              {\"origin\": \"example.net\","
-                      "               \"file\": \"example.net.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.net.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(3, server.getMemoryDataSrc(rrclass)->getZoneCount());
 }
 
 TEST_F(MemoryDatasrcConfigTest, replace) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(isc::datasrc::result::SUCCESS,
               server.getMemoryDataSrc(rrclass)->findZone(
@@ -179,31 +189,69 @@ TEST_F(MemoryDatasrcConfigTest, replace) {
     // should replace the old one.
     delete parser;
     parser = createAuthConfigParser(server, "datasources"); 
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.org\","
-                      "               \"file\": \"example.org.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
                       "              {\"origin\": \"example.net\","
-                      "               \"file\": \"example.net.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.net.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(2, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(isc::datasrc::result::NOTFOUND,
               server.getMemoryDataSrc(rrclass)->findZone(
                   Name("example.com")).code);
 }
 
+TEST_F(MemoryDatasrcConfigTest, exception) {
+    // Load a zone
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.com\","
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
+    EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+    EXPECT_EQ(isc::datasrc::result::SUCCESS,
+              server.getMemoryDataSrc(rrclass)->findZone(
+                  Name("example.com")).code);
+
+    // create a new parser, and try to load something. It will throw,
+    // the given master file should not exist
+    delete parser;
+    parser = createAuthConfigParser(server, "datasources");
+    EXPECT_THROW(parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.org\","
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
+                      "              {\"origin\": \"example.net\","
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/nonexistent.zone\"}]}]")), isc::dns::MasterLoadError);
+    // As that one throwed exception, it is not expected from us to
+    // commit it
+
+    // The original should be untouched
+    EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+    EXPECT_EQ(isc::datasrc::result::SUCCESS,
+              server.getMemoryDataSrc(rrclass)->findZone(
+                  Name("example.com")).code);
+}
+
 TEST_F(MemoryDatasrcConfigTest, remove) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
 
     delete parser;
     parser = createAuthConfigParser(server, "datasources"); 
-    parser->build(Element::fromJSON("[]"));
-    parser->commit();
+    EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
 }
 
@@ -212,9 +260,11 @@ TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \"example.zone\"},"
+                         "               \"file\": \"" TEST_DATA_DIR
+                         "/example.zone\"},"
                          "              {\"origin\": \"example.com\","
-                         "               \"file\": \"example.com.zone\"}]}]")),
+                         "               \"file\": \"" TEST_DATA_DIR
+                         "/example.com.zone\"}]}]")),
                  AuthConfigError);
 }
 

+ 123 - 13
src/bin/auth/tests/query_unittest.cc

@@ -29,24 +29,57 @@ 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(),
                                       RRTTL(3600)));
-namespace {
+RRsetPtr soa_rrset = RRsetPtr(new RRset(Name("example.com"),
+                                        RRClass::IN(), RRType::SOA(),
+                                        RRTTL(3600)));
+RRsetPtr ns_rrset(RRsetPtr(new RRset(Name("ns.example.com"),
+                                     RRClass::IN(), RRType::NS(),
+                                     RRTTL(3600))));
+RRsetPtr glue_a_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
+                                         RRClass::IN(), RRType::A(),
+                                         RRTTL(3600))));
+RRsetPtr glue_aaaa_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
+                                            RRClass::IN(), RRType::AAAA(),
+                                            RRTTL(3600))));
+RRsetPtr noglue_a_rrset(RRsetPtr(new RRset(Name("noglue.example.com"),
+                                         RRClass::IN(), RRType::A(),
+                                         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",
 // return NXDOMAIN for "nxdomain.example.com",
 // return NXRRSET for "nxrrset.example.com",
 // return CNAME for "cname.example.com",
-// else return DNAME
+// 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)))),
+        cname_rrset(RRsetPtr(new RRset(Name("cname.example.com"),
+                                       RRClass::IN(), RRType::CNAME(),
+                                       RRTTL(3600)))),
         mx_rrset_(new RRset(Name("mx.example.com"), RRClass::IN(),
             RRType::MX(), RRTTL(3600)))
     {
+        delegation_rrset->addRdata(rdata::generic::NS(
+                          Name("glue.ns.example.com")));
+        delegation_rrset->addRdata(rdata::generic::NS(
+                          Name("noglue.example.com")));
+        delegation_rrset->addRdata(rdata::generic::NS(
+                          Name("cname.example.com")));
+        delegation_rrset->addRdata(rdata::generic::NS(
+                          Name("example.org")));
+        cname_rrset->addRdata(rdata::generic::CNAME(
+                          Name("www.example.com")));
         mx_rrset_->addRdata(isc::dns::rdata::generic::MX(10,
             Name("www.example.com")));
         mx_rrset_->addRdata(isc::dns::rdata::generic::MX(20,
@@ -56,10 +89,14 @@ public:
     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;
     RRsetPtr mx_rrset_;
 };
 
@@ -74,22 +111,38 @@ MockZone::getClass() const {
 }
 
 Zone::FindResult
-MockZone::find(const Name& name, const RRType&) const {
+MockZone::find(const Name& name, const RRType& type,
+               const FindOptions options) const
+{
     // hardcode the find results
     if (name == Name("www.example.com")) {
-        return FindResult(SUCCESS, a_rrset);
+        return (FindResult(SUCCESS, a_rrset));
+    } else if (name == Name("glue.ns.example.com") && type == RRType::A() &&
+        options == FIND_GLUE_OK) {
+        return (FindResult(SUCCESS, glue_a_rrset));
+    } else if (name == Name("noglue.example.com") && type == RRType::A()) {
+        return (FindResult(SUCCESS, noglue_a_rrset));
+    } else if (name == Name("glue.ns.example.com") && type == RRType::AAAA() &&
+        options == FIND_GLUE_OK) {
+        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, RRsetPtr());
+        return (FindResult(DELEGATION, delegation_rrset));
+    } else if (name == Name("ns.example.com")) {
+        return (FindResult(DELEGATION, ns_rrset));
     } else if (name == Name("nxdomain.example.com")) {
-        return FindResult(NXDOMAIN, RRsetPtr());
+        return (FindResult(NXDOMAIN, RRsetPtr()));
     } else if (name == Name("nxrrset.example.com")) {
-        return FindResult(NXRRSET, RRsetPtr());
-    } else if (name == Name("cname.example.com")) {
-        return FindResult(CNAME, RRsetPtr());
+        return (FindResult(NXRRSET, RRsetPtr()));
+    } else if ((name == Name("cname.example.com"))) {
+        return (FindResult(CNAME, cname_rrset));
     } else if (name == Name("mx.example.com")) {
         return (FindResult(SUCCESS, mx_rrset_));
     } else {
-        return FindResult(DNAME, RRsetPtr());
+        return (FindResult(DNAME, RRsetPtr()));
     }
 }
 
@@ -118,25 +171,82 @@ TEST_F(QueryTest, noZone) {
 }
 
 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));
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
                                   Name("www.example.com"), RRClass::IN(),
                                   RRType::A()));
 
+    // Delegation
+    const Name delegation_name(Name("delegation.example.com"));
+    Query delegation_query(memory_datasrc, delegation_name, qtype, response);
+    delegation_query.process();
+    EXPECT_FALSE(response.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+                                  Name("delegation.example.com"),
+                                  RRClass::IN(), RRType::NS()));
+    // glue address records
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::AAAA()));
+    // noglue address records
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("noglue.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    // NS name has a CNAME
+    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("www.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    // NS name is out of zone
+    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("example.org"),
+                                  RRClass::IN(), RRType::A()));
+
     // NXDOMAIN
     const Name nxdomain_name(Name("nxdomain.example.com"));
     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) {

+ 1 - 1
src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec

@@ -1,5 +1,5 @@
 #
-# A QUERY message with unsupported version of EDNS..
+# A QUERY message with unsupported version of EDNS.
 #
 
 [header]

+ 1 - 0
src/bin/recurse/tests/Makefile.am

@@ -27,6 +27,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la

+ 16 - 2
src/bin/recurse/tests/recursor_config_unittest.cc

@@ -12,10 +12,24 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <cc/data.h>
+
+#include <asiolink/asiolink.h>
 
 #include <recurse/recursor.h>
-#include <testutils/srv_unittest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <testutils/srv_test.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::testutils;
+using namespace asiolink;
+using isc::UnitTestUtil;
 
 namespace {
 class RecursorConfig : public ::testing::Test {

+ 19 - 10
src/bin/recurse/tests/recursor_unittest.cc

@@ -12,10 +12,15 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
+#include <dns/name.h>
 
 #include <recurse/recursor.h>
-#include <testutils/srv_unittest.h>
+#include <dns/tests/unittest_util.h>
+#include <testutils/srv_test.h>
+
+using namespace isc::dns;
+using namespace isc::testutils;
+using isc::UnitTestUtil;
 
 namespace {
 const char* const TEST_PORT = "53535";
@@ -23,48 +28,52 @@ const char* const TEST_PORT = "53535";
 class RecursorTest : public SrvTestBase{
 protected:
     RecursorTest() : server(){}
+    virtual void processMessage() {
+        server.processMessage(*io_message, parse_message, response_obuffer,
+                              &dnsserv);
+    }
     Recursor server;
 };
 
 // Unsupported requests.  Should result in NOTIMP.
 TEST_F(RecursorTest, unsupportedRequest) {
-    UNSUPPORTED_REQUEST_TEST;
+    unsupportedRequest();
 }
 
 // Multiple questions.  Should result in FORMERR.
 TEST_F(RecursorTest, multiQuestion) {
-    MULTI_QUESTION_TEST; 
+    multiQuestion();
 }
 
 // Incoming data doesn't even contain the complete header.  Must be silently
 // dropped.
 TEST_F(RecursorTest, shortMessage) {
-    SHORT_MESSAGE_TEST;
+    shortMessage();
 }
 
 // Response messages.  Must be silently dropped, whether it's a valid response
 // or malformed or could otherwise cause a protocol error.
 TEST_F(RecursorTest, response) {
-    RESPONSE_TEST;
+    response();
 }
 
 // Query with a broken question
 TEST_F(RecursorTest, shortQuestion) {
-    SHORT_QUESTION_TEST;
+    shortQuestion();
 }
 
 // Query with a broken answer section
 TEST_F(RecursorTest, shortAnswer) {
-    SHORT_ANSWER_TEST;
+    shortAnswer();
 }
 
 // Query with unsupported version of EDNS.
 TEST_F(RecursorTest, ednsBadVers) {
-    EDNS_BADVERS_TEST;
+    ednsBadVers();
 }
 
 TEST_F(RecursorTest, AXFROverUDP) {
-    AXFR_OVER_UDP_TEST;
+    axfrOverUDP();
 }
 
 TEST_F(RecursorTest, AXFRFail) {

+ 1 - 1
src/lib/asiolink/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . tests internal
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)

+ 1 - 1
src/lib/asiolink/asiolink.h

@@ -235,7 +235,7 @@ public:
     /// that share the same \c io_service with the authoritative server.
     /// It will eventually be removed once the wrapper interface is
     /// generalized.
-    asio::io_service& get_io_service() { return io_service_.get_io_service(); };
+    asio::io_service& get_io_service() { return io_service_.get_io_service(); }
 private:
     DNSServiceImpl* impl_;
     IOService& io_service_;

+ 1 - 0
src/lib/asiolink/internal/Makefile.am

@@ -0,0 +1 @@
+SUBDIRS = tests

+ 37 - 0
src/lib/asiolink/internal/tests/Makefile.am

@@ -0,0 +1,37 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = udpdns_unittest.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+if USE_CLANGPP
+# We need to disable -Werror for any test that uses internal definitions of
+# ASIO when using clang++
+run_unittests_CXXFLAGS += -Wno-error
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 21 - 0
src/lib/asiolink/internal/tests/run_unittests.cc

@@ -0,0 +1,21 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return (RUN_ALL_TESTS());
+}

src/lib/asiolink/tests/udpdns_unittest.cc → src/lib/asiolink/internal/tests/udpdns_unittest.cc


+ 0 - 1
src/lib/asiolink/tests/Makefile.am

@@ -18,7 +18,6 @@ TESTS += run_unittests
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += asiolink_unittest.cc
-run_unittests_SOURCES += udpdns_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 72 - 40
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -17,6 +17,9 @@
 
 #include <config.h>
 
+#include <sys/socket.h>
+#include <sys/time.h>
+
 #include <string.h>
 
 #include <boost/lexical_cast.hpp>
@@ -32,19 +35,21 @@
 #include <dns/buffer.h>
 #include <dns/message.h>
 
+// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
+// In particular, we must not include asio.hpp in this file.
+// The asiolink module is primarily intended to be a wrapper that hide the
+// details of the underlying implementations.  We need to test the wrapper
+// level behaviors.  In addition, some compilers reject to compile this file
+// if we include asio.hpp unless we specify a special compiler option.
+// If we need to test something at the level of underlying ASIO and need
+// their definition, that test should go to asiolink/internal/tests.
 #include <asiolink/asiolink.h>
 #include <asiolink/iosocket.h>
-#include <asiolink/internal/tcpdns.h>
-#include <asiolink/internal/udpdns.h>
-
-#include <asio.hpp>
 
 using isc::UnitTestUtil;
 using namespace std;
 using namespace asiolink;
 using namespace isc::dns;
-using namespace asio;
-using asio::ip::udp;
 
 namespace {
 const char* const TEST_SERVER_PORT = "53535";
@@ -330,10 +335,30 @@ protected:
         // ... and this one will block until the send has completed
         io_service_->run_one();
 
-        // Now we attempt to recv() whatever was sent
-        const int ret = recv(sock_, buffer, size, MSG_DONTWAIT);
+        // Now we attempt to recv() whatever was sent.
+        // XXX: there's no guarantee the receiving socket can immediately get
+        // the packet.  Normally we can perform blocking recv to wait for it,
+        // but in theory it's even possible that the packet is lost.
+        // In order to prevent the test from hanging in such a worst case
+        // we add an ad hoc timeout.
+        const struct timeval timeo = { 10, 0 };
+        int recv_options = 0;
+        if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
+                       sizeof(timeo))) {
+            if (errno == ENOPROTOOPT) {
+                // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
+                // with the error of ENOPROTOOPT.  Since this is a workaround
+                // for rare error cases anyway, we simply switch to the
+                // "don't wait" mode.  If we still find an error in recv()
+                // can happen often we'll consider a more complete solution.
+                recv_options = MSG_DONTWAIT;
+            } else {
+                isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+            }
+        }
+        const int ret = recv(sock_, buffer, size, recv_options);
         if (ret < 0) {
-            isc_throw(IOError, "recvfrom failed");
+            isc_throw(IOError, "recvfrom failed: " << strerror(errno));
         }
         
         // Pass the message size back via the size parameter
@@ -411,8 +436,7 @@ protected:
     // has completed.
     class MockServer : public DNSServer {
     public:
-        explicit MockServer(asio::io_service& io_service,
-                            const asio::ip::address& addr, const uint16_t port,
+        explicit MockServer(IOService& io_service,
                             SimpleCallback* checkin = NULL,
                             DNSLookup* lookup = NULL,
                             DNSAnswer* answer = NULL) :
@@ -426,9 +450,7 @@ protected:
                         size_t length = 0)
         {}
 
-        void resume(const bool done) {
-            done_ = done;
-            io_.post(*this);
+        void resume(const bool) { // in our test this shouldn't be called
         }
 
         DNSServer* clone() {
@@ -443,7 +465,7 @@ protected:
         }
 
     protected:
-        asio::io_service& io_;
+        IOService& io_;
         bool done_;
 
     private:
@@ -462,8 +484,8 @@ protected:
     // This version of mock server just stops the io_service when it is resumed
     class MockServerStop : public MockServer {
         public:
-            explicit MockServerStop(asio::io_service& io_service, bool* done) :
-                MockServer(io_service, asio::ip::address(), 0),
+            explicit MockServerStop(IOService& io_service, bool* done) :
+                MockServer(io_service),
                 done_(done)
             {}
 
@@ -511,7 +533,6 @@ protected:
     string callback_address_;
     vector<uint8_t> callback_data_;
     int sock_;
-private:
     struct addrinfo* res_;
 };
 
@@ -640,14 +661,12 @@ TEST_F(ASIOLinkTest, recursiveSetupV6) {
 // full code coverage including error cases.
 TEST_F(ASIOLinkTest, recursiveSend) {
     setDNSService(true, false);
-    asio::io_service& io = io_service_->get_io_service();
 
     // Note: We use the test prot plus one to ensure we aren't binding
     // to the same port as the actual server
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    asio::ip::address addr = asio::ip::address::from_string(TEST_IPV4_ADDR);
 
-    MockServer server(io, addr, port, NULL, NULL, NULL);
+    MockServer server(*io_service_);
     RecursiveQuery rq(*dns_service_, singleAddress(TEST_IPV4_ADDR, port));
 
     Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
@@ -656,7 +675,7 @@ TEST_F(ASIOLinkTest, recursiveSend) {
 
     char data[4096];
     size_t size = sizeof(data);
-    EXPECT_NO_THROW(recvUDP(AF_INET, data, size));
+    ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
 
     Message m(Message::PARSE);
     InputBuffer ibuf(data, size);
@@ -672,34 +691,27 @@ TEST_F(ASIOLinkTest, recursiveSend) {
     EXPECT_EQ(q.getClass(), q2->getClass());
 }
 
-void
-receive_and_inc(udp::socket* socket, int* num) {
-    (*num) ++;
-    static char inbuff[512];
-    socket->async_receive(asio::buffer(inbuff, 512),
-        boost::bind(receive_and_inc, socket, num));
-}
-
 // Test it tries the correct amount of times before giving up
 TEST_F(ASIOLinkTest, recursiveTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
-    asio::io_service& service = io_service_->get_io_service();
 
     // Prepare the socket
-    uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    udp::socket socket(service, udp::v4());
-    socket.set_option(socket_base::reuse_address(true));
-    socket.bind(udp::endpoint(ip::address::from_string(TEST_IPV4_ADDR), port));
-    // And count the answers
-    int num = -1; // One is counted before the receipt of the first one
-    receive_and_inc(&socket, &num);
+    res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
+    sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+    if (sock_ < 0) {
+        isc_throw(IOError, "failed to open test socket");
+    }
+    if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+        isc_throw(IOError, "failed to bind test socket");
+    }
 
     // Prepare the server
     bool done(true);
-    MockServerStop server(service, &done);
+    MockServerStop server(*io_service_, &done);
 
     // Do the answer
+    const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
     RecursiveQuery query(*dns_service_, singleAddress(TEST_IPV4_ADDR, port),
         10, 2);
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
@@ -707,7 +719,27 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
     query.sendQuery(question, buffer, &server);
 
     // Run the test
-    service.run();
+    io_service_->run();
+
+    // Read up to 3 packets.  Use some ad hoc timeout to prevent an infinite
+    // block (see also recvUDP()).
+    const struct timeval timeo = { 10, 0 };
+    int recv_options = 0;
+    if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
+        if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP()
+            recv_options = MSG_DONTWAIT;
+        } else {
+            isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+        }
+    }
+    int num = 0;
+    do {
+        char inbuff[512];
+        if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
+            num = -1;
+            break;
+        }
+    } while (++num < 3);
 
     // The query should fail
     EXPECT_FALSE(done);

+ 2 - 2
src/lib/datasrc/cache.h

@@ -81,7 +81,7 @@ class HotCacheImpl;
 /// from the tail of the list.  This operation is not locked.  BIND 10
 /// does not currently use threads, but if it ever does (or if libdatasrc
 /// is ever used by a threaded application), this will need to be
-//revisited.
+/// revisited.
 class HotCache {
 private:
     /// \name Static definitions
@@ -164,7 +164,7 @@ public:
     ///
     /// Retrieves a record from the cache matching the given 
     /// query-tuple.  Returns true if one is found.  If it is a
-    /// posiitve cache entry, then 'rrset' is set to the cached
+    /// positive cache entry, then 'rrset' is set to the cached
     /// RRset.  For both positive and negative cache entries, 'flags'
     /// is set to the query response flags.  The cache entry is 
     /// then promoted to the head of the LRU queue.  (NOTE: Because

+ 2 - 2
src/lib/datasrc/data_source.cc

@@ -103,14 +103,14 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
                                new QueryTask(q, ns.getNSName(),
                                              Message::SECTION_ADDITIONAL,
                                              QueryTask::GLUE_QUERY,
-                                             QueryTask::GETADDITIONAL))); 
+                                             QueryTask::GETADDITIONAL)));
         } else if (rrset->getType() == RRType::MX()) {
             const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
             q.tasks().push(QueryTaskPtr(
                                new QueryTask(q, mx.getMXName(),
                                              Message::SECTION_ADDITIONAL,
                                              QueryTask::NOGLUE_QUERY,
-                                             QueryTask::GETADDITIONAL))); 
+                                             QueryTask::GETADDITIONAL)));
         }
     }
 }

+ 117 - 13
src/lib/datasrc/memory_datasrc.cc

@@ -15,9 +15,11 @@
 #include <map>
 #include <cassert>
 #include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
+#include <dns/masterload.h>
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/rbtree.h>
@@ -65,7 +67,7 @@ struct MemoryZone::MemoryZoneImpl {
      * access is without the impl_-> and it will get inlined anyway.
      */
     // Implementation of MemoryZone::add
-    result::Result add(const ConstRRsetPtr& rrset) {
+    result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
         // Sanitize input
         if (!rrset) {
             isc_throw(NullRRset, "The rrset provided is NULL");
@@ -80,7 +82,7 @@ struct MemoryZone::MemoryZoneImpl {
         }
         // Get the node
         DomainNode* node;
-        switch (domains_.insert(name, &node)) {
+        switch (domains->insert(name, &node)) {
             // Just check it returns reasonable results
             case DomainTree::SUCCEED:
             case DomainTree::ALREADYEXIST:
@@ -104,6 +106,15 @@ struct MemoryZone::MemoryZoneImpl {
         // 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
@@ -111,16 +122,84 @@ struct MemoryZone::MemoryZoneImpl {
         }
     }
 
+    /*
+     * Same as above, but it checks the return value and if it already exists,
+     * it throws.
+     */
+    void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
+            switch (add(set, domains)) {
+                case result::EXIST:
+                    isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
+                        set->toText());
+                case result::SUCCESS:
+                    return;
+                default:
+                    assert(0);
+            }
+    }
+
+    // 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
@@ -131,7 +210,18 @@ struct MemoryZone::MemoryZoneImpl {
         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));
@@ -166,13 +256,27 @@ MemoryZone::getClass() const {
 }
 
 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
 MemoryZone::add(const ConstRRsetPtr& rrset) {
-    return (impl_->add(rrset));
+    return (impl_->add(rrset, &impl_->domains_));
+}
+
+
+void
+MemoryZone::load(const string& filename) {
+    // Load it into a temporary tree
+    MemoryZoneImpl::DomainTree tmp;
+    masterLoad(filename.c_str(), getOrigin(), getClass(),
+        boost::bind(&MemoryZoneImpl::addFromLoad, impl_, _1, &tmp));
+    // If it went well, put it inside
+    tmp.swap(impl_->domains_);
+    // And let the old data die with tmp
 }
 
 /// Implementation details for \c MemoryDataSrc hidden from the public

+ 26 - 1
src/lib/datasrc/memory_datasrc.h

@@ -62,7 +62,8 @@ public:
     /// 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.
     ///
@@ -97,6 +98,30 @@ public:
         { }
     };
 
+    /// \brief Load zone from masterfile.
+    ///
+    /// This loads data from masterfile specified by filename. It replaces
+    /// current content. The masterfile parsing ability is kind of limited,
+    /// see isc::dns::masterLoad.
+    ///
+    /// This throws isc::dns::MasterLoadError if there is problem with loading
+    /// (missing file, malformed, it contains different zone, etc - see
+    /// isc::dns::masterLoad for details).
+    ///
+    /// In case of internal problems, OutOfZone, NullRRset or AssertError could
+    /// be thrown, but they should not be expected. Exceptions caused by
+    /// allocation may be thrown as well.
+    ///
+    /// If anything is thrown, the previous content is preserved (so it can
+    /// be used to update the data, but if user makes a typo, the old one
+    /// is kept).
+    ///
+    /// \param filename The master file to load.
+    ///
+    /// \todo We may need to split it to some kind of build and commit/abort.
+    ///     This will probably be needed when a better implementation of
+    ///     configuration reloading is written.
+    void load(const std::string& filename);
 private:
     /// \name Hidden private data
     //@{

+ 221 - 75
src/lib/datasrc/rbtree.h

@@ -38,8 +38,11 @@ namespace helper {
 /// 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 @@ operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
 
 template <typename T>
 class RBTree;
-/// \brief \c RBNode use by RBTree to store any data related to one domain name
 
+/// \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 @@ public:
     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 @@ public:
     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 @@ private:
 
     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 @@ private:
     /// 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 @@ RBNode<T>::RBNode() :
     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 @@ RBNode<T>::RBNode(const isc::dns::Name& name) :
     right_(NULL_NODE()),
     color_(RED),
     name_(name),
-    down_(NULL_NODE())
+    down_(NULL_NODE()),
+    callback_required_(false)
 {
 }
 
@@ -186,51 +215,55 @@ RBNode<T>::RBNode(const isc::dns::Name& name) :
 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 @@ public:
 
     /// \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 @@ public:
 
     /// \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,11 +407,21 @@ public:
     /// - 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);
     //@}
 
+    /// \brief Swaps two tree's contents.
+    ///
+    /// This acts the same as many std::*.swap functions, exchanges the
+    /// contents. This doesn't throw anything.
+    void swap(RBTree<T>& other) {
+        std::swap(root_, other.root_);
+        std::swap(NULLNODE, other.NULLNODE);
+        std::swap(node_count_, other.node_count_);
+    }
+
 private:
     /// \name RBTree balance functions
     //@{
@@ -315,8 +443,11 @@ private:
     /// 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
@@ -380,30 +511,39 @@ void RBTree<T> ::deleteHelper(RBNode<T> *root) {
     --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));
+    return (findHelper(name, &up_node, node, callback, callback_arg));
 }
 
-template <typename T>
+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;
 
@@ -431,12 +571,17 @@ RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_no
                 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;
@@ -527,14 +672,15 @@ void
 RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
     using namespace helper;
     const isc::dns::Name sub_name = node.name_ - base_name;
-    // using auto_ptr here is to avoid memory leak in case of exceptoin raised
+    // using auto_ptr here is to avoid memory leak in case of exception raised
     // 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();
-    //root node of sub tree, the initial color is BLACK
+    // root node of sub tree, the initial color is BLACK
     down_node->color_ = RBNode<T>::BLACK;
     ++node_count_;
     down_node.release();

+ 159 - 14
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -17,6 +17,7 @@
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
+#include <dns/masterload.h>
 
 #include <datasrc/memory_datasrc.h>
 
@@ -26,6 +27,9 @@ using namespace isc::dns;
 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:
@@ -138,18 +142,31 @@ public:
         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_;
 
@@ -159,7 +176,7 @@ public:
      * 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
@@ -170,6 +187,10 @@ public:
         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.
@@ -181,19 +202,32 @@ public:
      * \param name The name to ask for.
      * \param rrtype The RRType to ask of.
      * \param result The expected code of the result.
+     * \param check_answer Should a check against equality of the answer be
+     *     done?
      * \param answer The expected rrset, if any should be returned.
+     * \param zone Check different MemoryZone object than zone_ (if NULL,
+     *     uses zone_)
      */
     void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
-        const ConstRRsetPtr& answer = ConstRRsetPtr())
+                  bool check_answer = true,
+                  const ConstRRsetPtr& answer = ConstRRsetPtr(),
+                  MemoryZone *zone = NULL,
+                  Zone::FindOptions options = Zone::FIND_DEFAULT)
     {
+        if (!zone) {
+            zone = &zone_;
+        }
         // 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);
-            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);
+                }
+            });
     }
 };
 
@@ -219,7 +253,6 @@ TEST_F(MemoryZoneTest, add) {
     // 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_)));
@@ -231,6 +264,89 @@ TEST_F(MemoryZoneTest, add) {
     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)
+}
+
 /**
  * \brief Test searching.
  *
@@ -240,7 +356,6 @@ TEST_F(MemoryZoneTest, add) {
  */
 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_)));
@@ -248,8 +363,8 @@ TEST_F(MemoryZoneTest, find) {
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
 
     // These two should be successful
-    findTest(origin_, RRType::NS(), Zone::SUCCESS, rr_ns_);
-    findTest(ns_name_, RRType::A(), Zone::SUCCESS, rr_ns_a_);
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+    findTest(ns_name_, RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
 
     // These domain exist but don't have the provided RRType
     findTest(origin_, RRType::AAAA(), Zone::NXRRSET);
@@ -260,4 +375,34 @@ TEST_F(MemoryZoneTest, find) {
     findTest(Name("example.net"), RRType::A(), Zone::NXDOMAIN);
 }
 
+TEST_F(MemoryZoneTest, load) {
+    // Put some data inside the zone
+    EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, zone_.add(rr_ns_)));
+    // Loading with different origin should fail
+    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
+    // See the original data is still there, survived the exception
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+    // Create correct zone
+    MemoryZone rootzone(class_, Name("."));
+    // Try putting something inside
+    EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, rootzone.add(rr_ns_aaaa_)));
+    // Load the zone. It should overwrite/remove the above RRset
+    EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
+
+    // Now see there are some rrsets (we don't look inside, though)
+    findTest(Name("."), RRType::SOA(), Zone::SUCCESS, false, ConstRRsetPtr(),
+        &rootzone);
+    findTest(Name("."), RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
+        &rootzone);
+    findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
+        ConstRRsetPtr(), &rootzone);
+    // But this should no longer be here
+    findTest(ns_name_, RRType::AAAA(), Zone::NXDOMAIN, true, ConstRRsetPtr(),
+        &rootzone);
+
+    // Try loading zone that is wrong in a different way
+    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
+        MasterLoadError);
+}
+
 }

Fichier diff supprimé car celui-ci est trop grand
+ 85 - 0
src/lib/datasrc/tests/rbtree_unittest.cc


+ 4 - 0
src/lib/datasrc/tests/testdata/duplicate_rrset.zone

@@ -0,0 +1,4 @@
+example.org.    3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org.    3600 IN NS ns1.example.org.
+example.org.    3600 IN MX 10 mail.example.org.
+example.org.    3600 IN NS ns2.example.org.

+ 30 - 2
src/lib/datasrc/zone.h

@@ -100,6 +100,16 @@ public:
         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
+    };
+
     ///
     /// \name Constructors and Destructor.
     ///
@@ -150,6 +160,17 @@ public:
     /// - 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 @@ public:
     ///
     /// \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 @@ typedef boost::shared_ptr<const Zone> ConstZonePtr;
 }
 }
 
-#endif
+#endif  // __ZONE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 1
src/lib/datasrc/zonetable.h

@@ -25,7 +25,7 @@ namespace isc {
 namespace dns {
 class Name;
 class RRClass;
-};
+}
 
 namespace datasrc {
 

+ 10 - 9
src/lib/python/isc/config/module_spec.py

@@ -339,13 +339,14 @@ def _validate_spec_list(module_spec, full, data, errors):
 
     # check if there are items in our data that are not in the
     # specification
-    for item_name in data:
-        found = False
-        for spec_item in module_spec:
-            if spec_item["item_name"] == item_name:
-                found = True
-        if not found:
-            if errors != None:
-                errors.append("unknown item " + item_name)
-            validated = False
+    if data is not None:
+        for item_name in data:
+            found = False
+            for spec_item in module_spec:
+                if spec_item["item_name"] == item_name:
+                    found = True
+            if not found:
+                if errors != None:
+                    errors.append("unknown item " + item_name)
+                validated = False
     return validated

+ 4 - 0
src/lib/python/isc/config/tests/module_spec_test.py

@@ -109,6 +109,9 @@ class TestModuleSpec(unittest.TestCase):
         return dd.validate_command(cmd_name, params)
 
     def test_command_validation(self):
+        # tests for a command that doesn't take an argument
+        self.assertEqual(True, self.read_spec_file("spec2.spec").validate_command("shutdown", None));
+        self.assertEqual(False, self.read_spec_file("spec2.spec").validate_command("shutdown", '{"val": 1}'));
         self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_1.data", 'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_2.data",'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_3.data", 'cmd1'))
@@ -321,6 +324,7 @@ class TestModuleSpec(unittest.TestCase):
                }]
 
         errors = []
+        self.assertEqual(True, isc.config.module_spec._validate_spec_list(spec, True, None, None))
         self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, None))
         self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, errors))
         self.assertEqual(['unknown item does_not_exist'], errors)

+ 12 - 4
src/lib/testutils/Makefile.am

@@ -1,5 +1,13 @@
-SUBDIRS = testdata
+SUBDIRS = . testdata
 
-EXTRA_DIST = srv_test.h
-EXTRA_DIST += srv_unittest.h
-EXTRA_DIST += mockups.h
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS=$(B10_CXXFLAGS)
+
+if HAVE_GTEST
+lib_LTLIBRARIES = libtestutils.la
+
+libtestutils_la_SOURCES = srv_test.h srv_test.cc
+libtestutils_la_SOURCES += mockups.h
+libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+endif

+ 0 - 2
src/lib/testutils/README

@@ -1,4 +1,2 @@
 Here is some code used by more than one test. No code is used for bind10
 itself, only for testing.
-
-As it contains headers only currently, it does not compile here.

+ 272 - 0
src/lib/testutils/srv_test.cc

@@ -0,0 +1,272 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <netinet/in.h>
+
+#include <dns/message.h>
+#include <dns/rcode.h>
+
+#include <asiolink/asiolink.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <testutils/srv_test.h>
+
+using namespace isc::dns;
+using namespace asiolink;
+
+namespace isc {
+namespace testutils {
+const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
+
+const unsigned int QR_FLAG = 0x1;
+const unsigned int AA_FLAG = 0x2;
+const unsigned int TC_FLAG = 0x4;
+const unsigned int RD_FLAG = 0x8;
+const unsigned int RA_FLAG = 0x10;
+const unsigned int AD_FLAG = 0x20;
+const unsigned int CD_FLAG = 0x40;
+
+SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
+                             parse_message(new Message(Message::PARSE)),
+                             default_qid(0x1035),
+                             opcode(Opcode(Opcode::QUERY())),
+                             qname("www.example.com"),
+                             qclass(RRClass::IN()),
+                             qtype(RRType::A()), io_sock(NULL),
+                             io_message(NULL), endpoint(NULL),
+                             request_obuffer(0),
+                             request_renderer(request_obuffer),
+                             response_obuffer(new OutputBuffer(0))
+{}
+
+SrvTestBase::~SrvTestBase() {
+    delete io_message;
+    delete endpoint;
+}
+
+void
+SrvTestBase::createDataFromFile(const char* const datafile,
+                                const int protocol)
+{
+    delete io_message;
+    data.clear();
+
+    delete endpoint;
+
+    endpoint = IOEndpoint::create(protocol,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    UnitTestUtil::readWireData(datafile, data);
+    io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
+        &IOSocket::getDummyTCPSocket();
+    io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
+}
+
+void
+SrvTestBase::createRequestPacket(Message& message,
+                                 const int protocol)
+{
+    message.toWire(request_renderer);
+
+    delete io_message;
+
+    endpoint = IOEndpoint::create(protocol,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
+        &IOSocket::getDummyTCPSocket();
+    io_message = new IOMessage(request_renderer.getData(),
+                               request_renderer.getLength(),
+                               *io_sock, *endpoint);
+}
+
+// Unsupported requests.  Should result in NOTIMP.
+void
+SrvTestBase::unsupportedRequest() {
+    for (unsigned int i = 0; i < 16; ++i) {
+        // set Opcode to 'i', which iterators over all possible codes except
+        // the standard query and notify 
+        if (i == isc::dns::Opcode::QUERY().getCode() ||
+            i == isc::dns::Opcode::NOTIFY().getCode()) {
+            continue;
+        }
+        createDataFromFile("simplequery_fromWire.wire");
+        data[2] = ((i << 3) & 0xff);
+
+        parse_message->clear(isc::dns::Message::PARSE);
+        processMessage();
+        EXPECT_TRUE(dnsserv.hasAnswer());
+        headerCheck(*parse_message, default_qid, isc::dns::Rcode::NOTIMP(), i,
+                    QR_FLAG, 0, 0, 0, 0);
+    }
+}
+
+// Multiple questions.  Should result in FORMERR.
+void
+SrvTestBase::multiQuestion() {
+    createDataFromFile("multiquestion_fromWire.wire");
+    processMessage();
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+                opcode.getCode(), QR_FLAG, 2, 0, 0, 0);
+
+    isc::dns::QuestionIterator qit = parse_message->beginQuestion();
+    EXPECT_EQ(isc::dns::Name("example.com"), (*qit)->getName());
+    EXPECT_EQ(isc::dns::RRClass::IN(), (*qit)->getClass());
+    EXPECT_EQ(isc::dns::RRType::A(), (*qit)->getType());
+    ++qit;
+    EXPECT_EQ(isc::dns::Name("example.com"), (*qit)->getName());
+    EXPECT_EQ(isc::dns::RRClass::IN(), (*qit)->getClass());
+    EXPECT_EQ(isc::dns::RRType::AAAA(), (*qit)->getType());
+    ++qit;
+    EXPECT_TRUE(qit == parse_message->endQuestion());
+}
+
+// Incoming data doesn't even contain the complete header.  Must be silently
+// dropped.
+void
+SrvTestBase::shortMessage() {
+    createDataFromFile("shortmessage_fromWire");
+    processMessage();
+    EXPECT_FALSE(dnsserv.hasAnswer());
+}
+
+// Response messages.  Must be silently dropped, whether it's a valid response
+// or malformed or could otherwise cause a protocol error.
+void
+SrvTestBase::response() {
+    // A valid (although unusual) response 
+    createDataFromFile("simpleresponse_fromWire.wire");
+    processMessage();
+    EXPECT_FALSE(dnsserv.hasAnswer());
+
+    // A response with a broken question section.  must be dropped rather than
+    //returning FORMERR.
+    createDataFromFile("shortresponse_fromWire");
+    processMessage();
+    EXPECT_FALSE(dnsserv.hasAnswer());
+
+    // A response to iquery.  must be dropped rather than returning NOTIMP.
+    createDataFromFile("iqueryresponse_fromWire.wire");
+    processMessage();
+    EXPECT_FALSE(dnsserv.hasAnswer());
+}
+
+// Query with a broken question
+void
+SrvTestBase::shortQuestion() {
+    createDataFromFile("shortquestion_fromWire");
+    processMessage();
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    // Since the query's question is broken, the question section of the
+    // response should be empty.
+    headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+                opcode.getCode(), QR_FLAG, 0, 0, 0, 0);
+}
+
+// Query with a broken answer section
+void
+SrvTestBase::shortAnswer() {
+    createDataFromFile("shortanswer_fromWire.wire");
+    processMessage();
+    EXPECT_TRUE(dnsserv.hasAnswer());
+
+    // This is a bogus query, but question section is valid.  So the response
+    // should copy the question section.
+    headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    isc::dns::QuestionIterator qit = parse_message->beginQuestion();
+    EXPECT_EQ(isc::dns::Name("example.com"), (*qit)->getName());
+    EXPECT_EQ(isc::dns::RRClass::IN(), (*qit)->getClass());
+    EXPECT_EQ(isc::dns::RRType::A(), (*qit)->getType());
+    ++qit;
+    EXPECT_TRUE(qit == parse_message->endQuestion());
+}
+
+// Query with unsupported version of EDNS.
+void
+SrvTestBase::ednsBadVers() {
+    createDataFromFile("queryBadEDNS_fromWire.wire");
+    processMessage();
+    EXPECT_TRUE(dnsserv.hasAnswer());
+
+    // The response must have an EDNS OPT RR in the additional section,
+    // it will be added automatically at the render time.
+    // Note that the DNSSEC DO bit is cleared even if this bit in the query
+    // is set.  This is a limitation of the current implementation.
+    headerCheck(*parse_message, default_qid, isc::dns::Rcode::BADVERS(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 1);
+    EXPECT_FALSE(parse_message->getEDNS()); // EDNS isn't added at this point
+
+    isc::dns::InputBuffer ib(response_obuffer->getData(),
+                             response_obuffer->getLength());
+    isc::dns::Message parsed(isc::dns::Message::PARSE);
+    parsed.fromWire(ib);
+    EXPECT_EQ(isc::dns::Rcode::BADVERS(), parsed.getRcode());
+    isc::dns::ConstEDNSPtr edns(parsed.getEDNS());
+    ASSERT_TRUE(edns);
+    EXPECT_FALSE(edns->getDNSSECAwareness());
+}
+
+void
+SrvTestBase::axfrOverUDP() {
+    // AXFR over UDP is invalid and should result in FORMERR.
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                                       isc::dns::Name("example.com"),
+                                       isc::dns::RRClass::IN(),
+                                       isc::dns::RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    processMessage();
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, isc::dns::Rcode::FORMERR(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+}
+
+void
+headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
+            const uint16_t opcodeval, const unsigned int flags,
+            const unsigned int qdcount,
+            const unsigned int ancount, const unsigned int nscount,
+            const unsigned int arcount)
+{
+    EXPECT_EQ(qid, message.getQid());
+    EXPECT_EQ(rcode, message.getRcode());
+    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
+    EXPECT_EQ((flags & QR_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_QR));
+    EXPECT_EQ((flags & AA_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ((flags & TC_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_TC));
+    EXPECT_EQ((flags & RA_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_RA));
+    EXPECT_EQ((flags & RD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_RD));
+    EXPECT_EQ((flags & AD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_AD));
+    EXPECT_EQ((flags & CD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_CD));
+
+    EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
+    EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
+}
+} // end of namespace testutils
+} // end of namespace isc
+
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 82 - 126
src/lib/testutils/srv_test.h

@@ -12,10 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id: auth_srv_unittest.cc 3310 2010-10-21 23:10:24Z each $
-
-#include <config.h>
-
 #include <gtest/gtest.h>
 
 #include <dns/buffer.h>
@@ -27,139 +23,99 @@
 #include <dns/rrclass.h>
 #include <dns/rrtype.h>
 
-#include <cc/data.h>
-#include <cc/session.h>
-
-#include <xfr/xfrout_client.h>
-
-#include <auth/auth_srv.h>
-#include <asiolink/asiolink.h>
-
-#include <dns/tests/unittest_util.h>
 #include "mockups.h"
 
-using namespace std;
-using namespace isc::cc;
-using namespace isc::dns;
-using namespace isc::data;
-using namespace isc::xfr;
-using namespace asiolink;
-using isc::UnitTestUtil;
-
-namespace {
-const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
-
-// The base class for Auth and Recurse test case
-class SrvTestBase : public ::testing::Test {
-protected:
-    SrvTestBase() : request_message(Message::RENDER),
-                    parse_message(new Message(Message::PARSE)),
-                    default_qid(0x1035), opcode(Opcode(Opcode::QUERY())),
-                    qname("www.example.com"), qclass(RRClass::IN()),
-                    qtype(RRType::A()), io_sock(NULL), 
-                    io_message(NULL), endpoint(NULL),
-                    request_obuffer(0), request_renderer(request_obuffer),
-                    response_obuffer(new OutputBuffer(0))
-    {}
-    ~SrvTestBase() {
-        delete io_message;
-        delete endpoint;
-    }
-    MockSession notify_session;
-    MockServer dnsserv;
-    Message request_message;
-    MessagePtr parse_message;
-    const qid_t default_qid;
-    const Opcode opcode;
-    const Name qname;
-    const RRClass qclass;
-    const RRType qtype;
-    IOSocket* io_sock;
-    IOMessage* io_message;
-    const IOEndpoint* endpoint;
-    OutputBuffer request_obuffer;
-    MessageRenderer request_renderer;
-    OutputBufferPtr response_obuffer;
-    vector<uint8_t> data;
-
-    void createDataFromFile(const char* const datafile, int protocol);
-    void createRequestPacket(Message& message, const int protocol);
-};
-
-void
-SrvTestBase::createDataFromFile(const char* const datafile,
-                                const int protocol = IPPROTO_UDP)
-{
-    delete io_message;
-    data.clear();
-
-    delete endpoint;
-
-    endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
-    UnitTestUtil::readWireData(datafile, data);
-    io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
-        &IOSocket::getDummyTCPSocket();
-    io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
+namespace asiolink {
+class IOSocket;
+class IOMessage;
+class IOEndpoint;
 }
 
-void
-SrvTestBase::createRequestPacket(Message& message,
-                                 const int protocol = IPPROTO_UDP)
-{
-    message.toWire(request_renderer);
-
-    delete io_message;
-
-    endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
-    io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
-        &IOSocket::getDummyTCPSocket();
-    io_message = new IOMessage(request_renderer.getData(),
-                               request_renderer.getLength(),
-                               *io_sock, *endpoint);
-}
+namespace isc {
+namespace testutils {
+extern const char* const DEFAULT_REMOTE_ADDRESS;
 
 // These are flags to indicate whether the corresponding flag bit of the
-// DNS header is to be set in the test cases.  (Note that the flag values
+// DNS header is to be set in the test cases.  (The flag values
 // is irrelevant to their wire-format values)
-const unsigned int QR_FLAG = 0x1;
-const unsigned int AA_FLAG = 0x2;
-const unsigned int TC_FLAG = 0x4;
-const unsigned int RD_FLAG = 0x8;
-const unsigned int RA_FLAG = 0x10;
-const unsigned int AD_FLAG = 0x20;
-const unsigned int CD_FLAG = 0x40;
+extern const unsigned int QR_FLAG;
+extern const unsigned int AA_FLAG;
+extern const unsigned int TC_FLAG;
+extern const unsigned int RD_FLAG;
+extern const unsigned int RA_FLAG;
+extern const unsigned int AD_FLAG;
+extern const unsigned int CD_FLAG;
 
 void
-headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
+headerCheck(const isc::dns::Message& message, const isc::dns::qid_t qid,
+            const isc::dns::Rcode& rcode,
             const uint16_t opcodeval, const unsigned int flags,
             const unsigned int qdcount,
             const unsigned int ancount, const unsigned int nscount,
-            const unsigned int arcount)
-{
-    EXPECT_EQ(qid, message.getQid());
-    EXPECT_EQ(rcode, message.getRcode());
-    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
-    EXPECT_EQ((flags & QR_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_QR));
-    EXPECT_EQ((flags & AA_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ((flags & TC_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_TC));
-    EXPECT_EQ((flags & RA_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_RA));
-    EXPECT_EQ((flags & RD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_RD));
-    EXPECT_EQ((flags & AD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_AD));
-    EXPECT_EQ((flags & CD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_CD));
+            const unsigned int arcount);
 
-    EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
-    EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
-    EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
-}
+// The base class for Auth and Recurse test case
+class SrvTestBase : public ::testing::Test {
+protected:
+    SrvTestBase();
+    virtual ~SrvTestBase();
+
+    /// Let the server process a DNS message.
+    ///
+    /// The derived class implementation is expected to pass \c io_message,
+    /// \c parse_message, \c response_obuffer, and \c dnsserv to the server
+    /// implementation it is testing.
+    virtual void processMessage() = 0;
+
+    /// The following methods implement server independent test logic using
+    /// the template method pattern.  Each test calls \c processMessage()
+    /// to delegate the server-dependent behavior to the actual implementation
+    /// classes.
+    void unsupportedRequest();
+    void multiQuestion();
+    void shortMessage();
+    void response();
+    void shortQuestion();
+    void shortAnswer();
+    void ednsBadVers();
+    void axfrOverUDP();
+
+    /// Create DNS packet data from a file.
+    ///
+    /// It constructs wire-format DNS packet data from \c datafile in the
+    /// form of \c IOMessage in \c io_message.
+    /// The existing content of \c io_message, if any, will be deleted.
+    void createDataFromFile(const char* const datafile,
+                            int protocol = IPPROTO_UDP);
+
+    ///  Create DNS packet data from a message.
+    ///
+    /// It constructs wire-format DNS packet data from \c message in the
+    /// form of \c IOMessage in \c io_message.
+    /// The existing content of \c io_message, if any, will be deleted.
+    void createRequestPacket(isc::dns::Message& message,
+                             const int protocol = IPPROTO_UDP);
 
-}
+    MockSession notify_session;
+    MockServer dnsserv;
+    isc::dns::Message request_message;
+    isc::dns::MessagePtr parse_message;
+    const isc::dns::qid_t default_qid;
+    const isc::dns::Opcode opcode;
+    const isc::dns::Name qname;
+    const isc::dns::RRClass qclass;
+    const isc::dns::RRType qtype;
+    asiolink::IOSocket* io_sock;
+    asiolink::IOMessage* io_message;
+    const asiolink::IOEndpoint* endpoint;
+    isc::dns::OutputBuffer request_obuffer;
+    isc::dns::MessageRenderer request_renderer;
+    isc::dns::OutputBufferPtr response_obuffer;
+    std::vector<uint8_t> data;
+};
+} // end of namespace testutils
+} // end of namespace isc
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 0 - 158
src/lib/testutils/srv_unittest.h

@@ -1,158 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id: auth_srv_unittest.cc 3310 2010-10-21 23:10:24Z each $
-
-#include "srv_test.h"
-
-namespace {
-
-// Unsupported requests.  Should result in NOTIMP.
-#define UNSUPPORTED_REQUEST_TEST \
-    for (unsigned int i = 0; i < 16; ++i) { \
-        /* set Opcode to 'i', which iterators over all possible codes except \
-           the standard query and notify */ \
-        if (i == Opcode::QUERY().getCode() || \
-            i == Opcode::NOTIFY().getCode()) { \
-            continue; \
-        } \
-        createDataFromFile("simplequery_fromWire.wire"); \
-        data[2] = ((i << 3) & 0xff); \
- \
-        parse_message->clear(Message::PARSE); \
-        server.processMessage(*io_message, parse_message, response_obuffer, \
-                              &dnsserv); \
-        EXPECT_TRUE(dnsserv.hasAnswer()); \
-        headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG, \
-                    0, 0, 0, 0); \
-    }
-
-// Simple API check
-#define VERBOSE_TEST \
-    EXPECT_FALSE(server.getVerbose()); \
-    server.setVerbose(true); \
-    EXPECT_TRUE(server.getVerbose()); \
-    server.setVerbose(false); \
-    EXPECT_FALSE(server.getVerbose()); \
-
-
-// Multiple questions.  Should result in FORMERR.
-#define MULTI_QUESTION_TEST \
-    createDataFromFile("multiquestion_fromWire.wire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_TRUE(dnsserv.hasAnswer()); \
-    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(), \
-                QR_FLAG, 2, 0, 0, 0); \
- \
-    QuestionIterator qit = parse_message->beginQuestion(); \
-    EXPECT_EQ(Name("example.com"), (*qit)->getName()); \
-    EXPECT_EQ(RRClass::IN(), (*qit)->getClass()); \
-    EXPECT_EQ(RRType::A(), (*qit)->getType()); \
-    ++qit; \
-    EXPECT_EQ(Name("example.com"), (*qit)->getName()); \
-    EXPECT_EQ(RRClass::IN(), (*qit)->getClass()); \
-    EXPECT_EQ(RRType::AAAA(), (*qit)->getType()); \
-    ++qit; \
-    EXPECT_TRUE(qit == parse_message->endQuestion());
-
-// Incoming data doesn't even contain the complete header.  Must be silently
-// dropped.
-#define SHORT_MESSAGE_TEST \
-    createDataFromFile("shortmessage_fromWire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_FALSE(dnsserv.hasAnswer());
-
-// Response messages.  Must be silently dropped, whether it's a valid response
-// or malformed or could otherwise cause a protocol error.
-#define RESPONSE_TEST \
-    /* A valid (although unusual) response */\
-    createDataFromFile("simpleresponse_fromWire.wire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_FALSE(dnsserv.hasAnswer()); \
- \
-    /* A response with a broken question section.  must be dropped rather than \
-       returning FORMERR. */\
-    createDataFromFile("shortresponse_fromWire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_FALSE(dnsserv.hasAnswer()); \
- \
-    /* A response to iquery.  must be dropped rather than returning NOTIMP. */\
-    createDataFromFile("iqueryresponse_fromWire.wire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_FALSE(dnsserv.hasAnswer());
-
-// Query with a broken question
-#define SHORT_QUESTION_TEST \
-    createDataFromFile("shortquestion_fromWire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_TRUE(dnsserv.hasAnswer()); \
-    /* Since the query's question is broken, the question section of the \
-       response should be empty. */\
-    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(), \
-                QR_FLAG, 0, 0, 0, 0);
-
-// Query with a broken answer section
-#define SHORT_ANSWER_TEST \
-    createDataFromFile("shortanswer_fromWire.wire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_TRUE(dnsserv.hasAnswer()); \
- \
-    /* This is a bogus query, but question section is valid.  So the response \
-       should copy the question section. */ \
-    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(), \
-                QR_FLAG, 1, 0, 0, 0); \
- \
-    QuestionIterator qit = parse_message->beginQuestion(); \
-    EXPECT_EQ(Name("example.com"), (*qit)->getName()); \
-    EXPECT_EQ(RRClass::IN(), (*qit)->getClass()); \
-    EXPECT_EQ(RRType::A(), (*qit)->getType()); \
-    ++qit; \
-    EXPECT_TRUE(qit == parse_message->endQuestion());
-
-// Query with unsupported version of EDNS.
-#define EDNS_BADVERS_TEST \
-    createDataFromFile("queryBadEDNS_fromWire.wire"); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_TRUE(dnsserv.hasAnswer()); \
- \
-    /* The response must have an EDNS OPT RR in the additional section, \
-       it will be added automatically at the render time.
-       Note that the DNSSEC DO bit is cleared even if this bit in the query \
-       is set.  This is a limitation of the current implementation. */ \
-    headerCheck(*parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(), \
-                QR_FLAG, 1, 0, 0, 1); \
-    EXPECT_FALSE(parse_message->getEDNS()); /* EDNS isn't added at this point */ \
- \
-    InputBuffer ib(response_obuffer->getData(), response_obuffer->getLength()); \
-    Message parsed(Message::PARSE); \
-    parsed.fromWire(ib); \
-    EXPECT_EQ(Rcode::BADVERS(), parsed.getRcode()); \
-    ConstEDNSPtr edns(parsed.getEDNS()); \
-    ASSERT_TRUE(edns); \
-    EXPECT_FALSE(edns->getDNSSECAwareness());
-
-
-#define AXFR_OVER_UDP_TEST \
-    /* AXFR over UDP is invalid and should result in FORMERR. */\
-    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid, \
-                         Name("example.com"), RRClass::IN(), \
-                         RRType::AXFR()); \
-    createRequestPacket(request_message, IPPROTO_UDP); \
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv); \
-    EXPECT_TRUE(dnsserv.hasAnswer()); \
-    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(), \
-                QR_FLAG, 1, 0, 0, 0);
-
-}
-

+ 3 - 0
src/lib/testutils/testdata/Makefile.am

@@ -4,6 +4,7 @@ BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
 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,8 @@ EXTRA_DIST += shortquestion_fromWire
 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
 EXTRA_DIST += example.sqlite3

+ 3 - 0
src/lib/testutils/testdata/example.com.zone

@@ -0,0 +1,3 @@
+example.com.    3600    IN  SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com.    3600    IN  NS ns.example.com.
+ns.example.com.	3600    IN  A 192.0.2.1

+ 3 - 0
src/lib/testutils/testdata/example.net.zone

@@ -0,0 +1,3 @@
+example.net.    3600    IN  SOA ns.example.net. admin.example.net. 1234 3600 1800 2419200 7200
+example.net.    3600    IN  NS ns.example.net.
+ns.example.net.	3600    IN  A 192.0.2.1

+ 3 - 0
src/lib/testutils/testdata/example.org.zone

@@ -0,0 +1,3 @@
+example.org.    3600    IN  SOA ns.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org.    3600    IN  NS ns.example.org.
+ns.example.org.	3600    IN  A 192.0.2.1

+ 3 - 0
src/lib/testutils/testdata/example.zone

@@ -0,0 +1,3 @@
+example.com.    3600    IN  SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com.    3600    IN  NS ns.example.com.
+ns.example.com.	3600    IN  A 192.0.2.1

+ 8 - 0
src/lib/testutils/testdata/iquery_fromWire.spec

@@ -0,0 +1,8 @@
+#
+# An IQUERY message
+#
+
+[header]
+opcode: iquery
+[question]
+# use default

+ 9 - 0
src/lib/testutils/testdata/iquery_response_fromWire.spec

@@ -0,0 +1,9 @@
+#
+# A response to IQUERY message (NOTIMP)
+#
+
+[header]
+qr: response
+opcode: iquery
+rcode: notimp
+qdcount: 0