Parcourir la source

[1443] Merge branch 'master' into merge1443

two options have been added to boss's __init__(), conflict resolved.
Conflicts:
	src/bin/bind10/bind10_src.py.in
	src/bin/bind10/tests/bind10_test.py.in
Jelte Jansen il y a 13 ans
Parent
commit
52b36c921e
40 fichiers modifiés avec 1138 ajouts et 575 suppressions
  1. 41 2
      ChangeLog
  2. 20 2
      configure.ac
  3. 2 2
      doc/guide/bind10-guide.xml
  4. 61 71
      src/bin/auth/query.cc
  5. 104 23
      src/bin/auth/query.h
  6. 143 1
      src/bin/auth/tests/query_unittest.cc
  7. 12 0
      src/bin/bind10/bind10.xml
  8. 32 23
      src/bin/bind10/bind10_src.py.in
  9. 63 0
      src/bin/bind10/tests/bind10_test.py.in
  10. 8 6
      src/bin/bindctl/bindcmd.py
  11. 14 23
      src/bin/bindctl/moduleinfo.py
  12. 1 2
      src/bin/bindctl/tests/bindctl_test.py
  13. 15 30
      src/bin/xfrout/b10-xfrout.8
  14. 12 11
      src/bin/xfrout/b10-xfrout.xml
  15. 1 0
      src/bin/zonemgr/tests/.gitignore
  16. 5 3
      src/bin/zonemgr/zonemgr_messages.mes
  17. 2 2
      src/lib/datasrc/Makefile.am
  18. 2 1
      src/lib/datasrc/data_source.cc
  19. 4 2
      src/lib/datasrc/datasrc_messages.mes
  20. 451 288
      src/lib/datasrc/memory_datasrc.cc
  21. 7 2
      src/lib/datasrc/rbtree.h
  22. 4 3
      src/lib/datasrc/tests/rbnode_rrset_unittest.cc
  23. 8 2
      src/lib/datasrc/tests/testdata/contexttest.zone
  24. 17 7
      src/lib/datasrc/tests/zone_finder_context_unittest.cc
  25. 1 1
      src/lib/dns/python/Makefile.am
  26. 1 1
      src/lib/dns/rrset.h
  27. 4 3
      src/lib/dns/tests/rrset_unittest.cc
  28. 2 2
      src/lib/log/logger_manager_impl.cc
  29. 2 2
      src/lib/python/isc/acl/Makefile.am
  30. 17 8
      src/lib/python/isc/cc/session.py
  31. 30 1
      src/lib/python/isc/cc/tests/session_test.py
  32. 11 8
      src/lib/python/isc/config/config_data.py
  33. 1 0
      src/lib/python/isc/config/tests/config_data_test.py
  34. 1 1
      src/lib/python/isc/datasrc/Makefile.am
  35. 1 0
      src/lib/python/isc/datasrc/tests/.gitignore
  36. 1 1
      src/lib/python/isc/log/Makefile.am
  37. 1 1
      src/lib/python/isc/util/cio/Makefile.am
  38. 1 1
      src/lib/util/io/Makefile.am
  39. 1 1
      src/lib/util/pyunittests/Makefile.am
  40. 34 38
      tests/lettuce/features/nsec3_auth.feature

+ 41 - 2
ChangeLog

@@ -1,4 +1,43 @@
-405.?	[bug]		jinmei
+411.   [func]          muks
+	Add a -i/--no-kill command-line argument to bind10, which stops
+	it from sending SIGTERM and SIGKILL to other b10 processes when
+	they're shutting down.
+	(Trac #1819, git 774554f46b20ca5ec2ef6c6d5e608114f14e2102)
+
+410.	[bug]		jinmei
+	Python CC library now ensures write operations transmit all given
+	data (unless an error happens).  Previously it didn't check the
+	size of transmitted data, which could result in partial write on
+	some systems (notably on OpenBSD) and subsequently cause system
+	hang up or other broken state.  This fix specifically solves start
+	up failure on OpenBSD.
+	(Trac #1829, git 5e5a33213b60d89e146cd5e47d65f3f9833a9297)
+
+409.	[bug]		jelte
+	Fixed a parser bug in bindctl that could make bindctl crash. Also
+	improved 'command help' output; argument order is now shown
+	correctly, and parameter descriptions are shown as well.
+	(Trac #1172, git bec26c6137c9b0a59a3a8ca0f55a17cfcb8a23de)
+
+408.	[bug]		stephen, jinmei
+	b10-auth now filters out duplicate RRsets when building a
+	response message using the new query handling logic.  It's
+	currently only used with the in-memory data source, but will
+	also be used for others soon.
+	(Trac #1688, git b77baca56ffb1b9016698c00ae0a1496d603d197)
+
+407.    [build]		haikuo
+	Remove "--enable-boost-threads" switch in configure command. This
+	thread lock mechanism is useless for bind10 and causes performance 
+	hits. 
+	(Trac #1680, git 9c4d0cadf4adc802cc41a2610dc2c30b25aad728)
+
+406.	[bug]		muks
+	On platforms such as OpenBSD where pselect() is not available,
+	make a wrapper around select() in perfdhcp.
+	(Trac #1639, git 6ea0b1d62e7b8b6596209291aa6c8b34b8e73191)
+
+405.	[bug]		jinmei
 	Make sure disabling Boost threads if the default configuration is
 	Make sure disabling Boost threads if the default configuration is
 	to disable it for the system.  This fixes a crash and hang up
 	to disable it for the system.  This fixes a crash and hang up
 	problem on OpenBSD, where the use of Boost thread could be
 	problem on OpenBSD, where the use of Boost thread could be
@@ -7,7 +46,7 @@
 	states between a library and a program.  Explicitly forcing the
 	states between a library and a program.  Explicitly forcing the
 	original default throughout the BIND 10 build environment will
 	original default throughout the BIND 10 build environment will
 	prevent this from happening.
 	prevent this from happening.
-	(Trac #1727, git TBD)
+	(Trac #1727, git 23f9c3670b544c5f8105958ff148aeba050bc1b4)
 
 
 404.	[bug]		naokikambe
 404.	[bug]		naokikambe
 	The statistic counters are now properly accumulated across multiple
 	The statistic counters are now properly accumulated across multiple

+ 20 - 2
configure.ac

@@ -123,6 +123,9 @@ case "$host" in
 *-netbsd*)
 *-netbsd*)
 	SET_ENV_LIBRARY_PATH=yes
 	SET_ENV_LIBRARY_PATH=yes
 	;;
 	;;
+*-openbsd*)
+	SET_ENV_LIBRARY_PATH=yes
+	;;
 esac
 esac
 AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
 AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
 AC_SUBST(SET_ENV_LIBRARY_PATH)
 AC_SUBST(SET_ENV_LIBRARY_PATH)
@@ -270,7 +273,7 @@ AC_DEFUN([BIND10_CXX_TRY_FLAG], [
   bind10_save_CXXFLAGS="$CXXFLAGS"
   bind10_save_CXXFLAGS="$CXXFLAGS"
   CXXFLAGS="$CXXFLAGS $1"
   CXXFLAGS="$CXXFLAGS $1"
 
 
-  AC_LINK_IFELSE([int main(void){ return 0;}],
+  AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
                  [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
                  [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
   CXXFLAGS="$bind10_save_CXXFLAGS"
   CXXFLAGS="$bind10_save_CXXFLAGS"
 
 
@@ -780,7 +783,22 @@ if test "${boost_include_path}" ; then
 fi
 fi
 AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
 AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
   AC_MSG_ERROR([Missing required header files.]))
-CPPFLAGS="$CPPFLAGS_SAVES"
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly.  In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
 AC_SUBST(BOOST_INCLUDES)
 AC_SUBST(BOOST_INCLUDES)
 
 
 # I can't get some of the #include <asio.hpp> right without this
 # I can't get some of the #include <asio.hpp> right without this

+ 2 - 2
doc/guide/bind10-guide.xml

@@ -2865,7 +2865,7 @@ Logging/loggers[0]/output_options[0]/maxver	0	integer	(default)
 
 
           <screen>&gt; <userinput> config set Logging/loggers[0]/output_options[0]/destination file</userinput>
           <screen>&gt; <userinput> config set Logging/loggers[0]/output_options[0]/destination file</userinput>
 &gt; <userinput> config set Logging/loggers[0]/output_options[0]/output /var/log/bind10.log</userinput>
 &gt; <userinput> config set Logging/loggers[0]/output_options[0]/output /var/log/bind10.log</userinput>
-&gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxsize 30000</userinput>
+&gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxsize 204800</userinput>
 &gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxver 8</userinput>
 &gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxver 8</userinput>
 </screen>
 </screen>
 
 
@@ -2888,7 +2888,7 @@ Logging/loggers[0]/additive	false	boolean	(default)
 Logging/loggers[0]/output_options[0]/destination	"file"	string	(modified)
 Logging/loggers[0]/output_options[0]/destination	"file"	string	(modified)
 Logging/loggers[0]/output_options[0]/output	"/var/log/bind10.log"	string	(modified)
 Logging/loggers[0]/output_options[0]/output	"/var/log/bind10.log"	string	(modified)
 Logging/loggers[0]/output_options[0]/flush	false	boolean	(default)
 Logging/loggers[0]/output_options[0]/flush	false	boolean	(default)
-Logging/loggers[0]/output_options[0]/maxsize	30000	integer	(modified)
+Logging/loggers[0]/output_options[0]/maxsize	204800	integer	(modified)
 Logging/loggers[0]/output_options[0]/maxver	8	integer	(modified)
 Logging/loggers[0]/output_options[0]/maxver	8	integer	(modified)
 </screen>
 </screen>
 
 

+ 61 - 71
src/bin/auth/query.cc

@@ -15,6 +15,7 @@
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/rcode.h>
 #include <dns/rcode.h>
 #include <dns/rrtype.h>
 #include <dns/rrtype.h>
+#include <dns/rrset.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 
 
 #include <datasrc/client.h>
 #include <datasrc/client.h>
@@ -25,7 +26,9 @@
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
 #include <boost/function.hpp>
 #include <boost/function.hpp>
 
 
+#include <cassert>
 #include <algorithm>            // for std::max
 #include <algorithm>            // for std::max
+#include <functional>
 #include <vector>
 #include <vector>
 
 
 using namespace std;
 using namespace std;
@@ -33,33 +36,9 @@ using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 using namespace isc::dns::rdata;
 using namespace isc::dns::rdata;
 
 
-// Commonly used helper callback object for vector<ConstRRsetPtr> to
-// insert them to (the specified section of) a message.
-namespace {
-class RRsetInserter {
-public:
-    RRsetInserter(Message& msg, Message::Section section, bool dnssec) :
-        msg_(msg), section_(section), dnssec_(dnssec)
-    {}
-    void operator()(const ConstRRsetPtr& rrset) {
-        /*
-         * FIXME:
-         * The const-cast is wrong, but the Message interface seems
-         * to insist.
-         */
-        msg_.addRRset(section_,
-                      boost::const_pointer_cast<AbstractRRset>(rrset),
-                      dnssec_);
-    }
-
-private:
-    Message& msg_;
-    const Message::Section section_;
-    const bool dnssec_;
-};
-
 // This is a "constant" vector storing desired RR types for the additional
 // This is a "constant" vector storing desired RR types for the additional
 // section.  The vector is filled first time it's used.
 // section.  The vector is filled first time it's used.
+namespace {
 const vector<RRType>&
 const vector<RRType>&
 A_AND_AAAA() {
 A_AND_AAAA() {
     static vector<RRType> needed_types;
     static vector<RRType> needed_types;
@@ -69,33 +48,58 @@ A_AND_AAAA() {
     }
     }
     return (needed_types);
     return (needed_types);
 }
 }
+}
+
+namespace isc {
+namespace auth {
 
 
-// A wrapper for ZoneFinder::Context::getAdditional() so we don't include
-// duplicate RRs.  This is not efficient, and we should actually unify
-// this at the end of the process() method.  See also #1688.
 void
 void
-getAdditional(const Name& qname, RRType qtype,
-              ZoneFinder::Context& ctx, vector<ConstRRsetPtr>& results)
+Query::ResponseCreator::addRRset(isc::dns::Message& message,
+                                 const isc::dns::Message::Section section,
+                                 const ConstRRsetPtr& rrset, const bool dnssec)
 {
 {
-    vector<ConstRRsetPtr> additionals;
-    ctx.getAdditional(A_AND_AAAA(), additionals);
-
-    vector<ConstRRsetPtr>::const_iterator it = additionals.begin();
-    vector<ConstRRsetPtr>::const_iterator it_end = additionals.end();
-    for (; it != it_end; ++it) {
-        if ((qtype == (*it)->getType() || qtype == RRType::ANY()) &&
-            qname == (*it)->getName()) {
-            continue;
-        }
-        results.push_back(*it);
+    /// Is this RRset already in the list of RRsets added to the message?
+    const std::vector<const AbstractRRset*>::const_iterator i =
+        std::find_if(added_.begin(), added_.end(),
+                     std::bind1st(Query::ResponseCreator::IsSameKind(),
+                                  rrset.get()));
+    if (i == added_.end()) {
+        // No - add it to both the message and the list of RRsets processed.
+        // The const-cast is wrong, but the message interface seems to insist.
+        message.addRRset(section,
+                         boost::const_pointer_cast<AbstractRRset>(rrset),
+                         dnssec);
+        added_.push_back(rrset.get());
     }
     }
 }
 }
 
 
+void
+Query::ResponseCreator::create(Message& response,
+                               const vector<ConstRRsetPtr>& answers,
+                               const vector<ConstRRsetPtr>& authorities,
+                               const vector<ConstRRsetPtr>& additionals,
+                               const bool dnssec)
+{
+    // Inserter should be reset each time the query is reset, so should be
+    // empty at this point.
+    assert(added_.empty());
+
+    // Add the RRsets to the message.  The order of sections is important,
+    // as the ResponseCreator remembers RRsets added and will not add
+    // duplicates.  Adding in the order answer, authory, additional will
+    // guarantee that if there are duplicates, the single RRset added will
+    // appear in the most important section.
+    BOOST_FOREACH(const ConstRRsetPtr& rrset, answers) {
+        addRRset(response, Message::SECTION_ANSWER, rrset, dnssec);
+    }
+    BOOST_FOREACH(const ConstRRsetPtr& rrset, authorities) {
+        addRRset(response, Message::SECTION_AUTHORITY, rrset, dnssec);
+    }
+    BOOST_FOREACH(const ConstRRsetPtr& rrset, additionals) {
+        addRRset(response, Message::SECTION_ADDITIONAL, rrset, dnssec);
+    }
 }
 }
 
 
-namespace isc {
-namespace auth {
-
 void
 void
 Query::addSOA(ZoneFinder& finder) {
 Query::addSOA(ZoneFinder& finder) {
     ZoneFinderContextPtr soa_ctx = finder.find(finder.getOrigin(),
     ZoneFinderContextPtr soa_ctx = finder.find(finder.getOrigin(),
@@ -154,14 +158,10 @@ Query::addNXDOMAINProofByNSEC(ZoneFinder& finder, ConstRRsetPtr nsec) {
         isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
         isc_throw(BadNSEC, "Unexpected result for wildcard NXDOMAIN proof");
     }
     }
 
 
-    // Add the (no-) wildcard proof only when it's different from the NSEC
-    // that proves NXDOMAIN; sometimes they can be the same.
-    // Note: name comparison is relatively expensive.  When we are at the
-    // stage of performance optimization, we should consider optimizing this
-    // for some optimized data source implementations.
-    if (nsec->getName() != fcontext->rrset->getName()) {
-        authorities_.push_back(fcontext->rrset);
-    }
+    // Add the (no-) wildcard proof.  This can be the same NSEC we already
+    // added, but we'd add it here anyway; duplicate checks will take place
+    // later in a unified manner.
+    authorities_.push_back(fcontext->rrset);
 }
 }
 
 
 uint8_t
 uint8_t
@@ -261,11 +261,8 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
         fcontext->rrset->getRdataCount() == 0) {
         fcontext->rrset->getRdataCount() == 0) {
         isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
         isc_throw(BadNSEC, "Unexpected result for no match QNAME proof");
     }
     }
-   
-    if (nsec->getName() != fcontext->rrset->getName()) {
-        // one NSEC RR proves wildcard_nxrrset that no matched QNAME.
-        authorities_.push_back(fcontext->rrset);
-    }
+
+    authorities_.push_back(fcontext->rrset);
 }
 }
 
 
 void
 void
@@ -332,7 +329,7 @@ Query::addAuthAdditional(ZoneFinder& finder,
                   finder.getOrigin().toText());
                   finder.getOrigin().toText());
     }
     }
     authorities_.push_back(ns_context->rrset);
     authorities_.push_back(ns_context->rrset);
-    getAdditional(*qname_, *qtype_, *ns_context, additionals);
+    ns_context->getAdditional(A_AND_AAAA(), additionals);
 }
 }
 
 
 namespace {
 namespace {
@@ -468,7 +465,7 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
             }
             }
 
 
             // Retrieve additional records for the answer
             // Retrieve additional records for the answer
-            getAdditional(*qname_, *qtype_, *db_context, additionals_);
+            db_context->getAdditional(A_AND_AAAA(), additionals_);
 
 
             // If apex NS records haven't been provided in the answer
             // If apex NS records haven't been provided in the answer
             // section, insert apex NS records into the authority section
             // section, insert apex NS records into the authority section
@@ -534,7 +531,8 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
             break;
             break;
     }
     }
 
 
-    createResponse();
+    response_creator_.create(*response_, answers_, authorities_, additionals_,
+                             dnssec_);
 }
 }
 
 
 void
 void
@@ -552,16 +550,6 @@ Query::initialize(datasrc::DataSourceClient& datasrc_client,
 }
 }
 
 
 void
 void
-Query::createResponse() {
-    for_each(answers_.begin(), answers_.end(),
-             RRsetInserter(*response_, Message::SECTION_ANSWER, dnssec_));
-    for_each(authorities_.begin(), authorities_.end(),
-             RRsetInserter(*response_, Message::SECTION_AUTHORITY, dnssec_));
-    for_each(additionals_.begin(), additionals_.end(),
-             RRsetInserter(*response_, Message::SECTION_ADDITIONAL, dnssec_));
-}
-
-void
 Query::reset() {
 Query::reset() {
     datasrc_client_ = NULL;
     datasrc_client_ = NULL;
     qname_ = NULL;
     qname_ = NULL;
@@ -570,6 +558,7 @@ Query::reset() {
     answers_.clear();
     answers_.clear();
     authorities_.clear();
     authorities_.clear();
     additionals_.clear();
     additionals_.clear();
+    response_creator_.clear();
 }
 }
 
 
 bool
 bool
@@ -601,7 +590,8 @@ Query::processDSAtChild() {
         }
         }
     }
     }
 
 
-    createResponse();
+    response_creator_.create(*response_, answers_, authorities_, additionals_,
+                             dnssec_);
     return (true);
     return (true);
 }
 }
 
 

+ 104 - 23
src/bin/auth/query.h

@@ -20,6 +20,7 @@
 
 
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 
 
+#include <functional>
 #include <vector>
 #include <vector>
 
 
 namespace isc {
 namespace isc {
@@ -36,13 +37,6 @@ class DataSourceClient;
 
 
 namespace auth {
 namespace auth {
 
 
-/// \brief Initial reserved size for the vectors in Query
-///
-/// The value is larger than we expect the vectors to even become, and
-/// has been chosen arbitrarily. The reason to set them quite high is
-/// to prevent reallocation on addition.
-const size_t RESERVE_RRSETS = 64;
-
 /// The \c Query class represents a standard DNS query that encapsulates
 /// The \c Query class represents a standard DNS query that encapsulates
 /// processing logic to answer the query.
 /// processing logic to answer the query.
 ///
 ///
@@ -75,6 +69,12 @@ const size_t RESERVE_RRSETS = 64;
 /// we keep this name at the moment.
 /// we keep this name at the moment.
 class Query : boost::noncopyable {
 class Query : boost::noncopyable {
 private:
 private:
+    /// \brief Initial reserved size for the vectors in Query
+    ///
+    /// The value is larger than we expect the vectors to even become, and
+    /// has been chosen arbitrarily. The reason to set them quite high is
+    /// to prevent reallocation on addition.
+    static const size_t RESERVE_RRSETS = 64;
 
 
     /// \brief Adds a SOA.
     /// \brief Adds a SOA.
     ///
     ///
@@ -251,19 +251,6 @@ private:
                     const isc::dns::Name& qname, const isc::dns::RRType& qtype,
                     const isc::dns::Name& qname, const isc::dns::RRType& qtype,
                     isc::dns::Message& response, bool dnssec = false);
                     isc::dns::Message& response, bool dnssec = false);
 
 
-    /// \brief Fill in the response sections
-    ///
-    /// This is the final step of the process() method, and within
-    /// that method, it should be called before it returns (if any
-    /// response data is to be added)
-    ///
-    /// This will take each RRset collected in answers_, authorities_, and
-    /// additionals_, and add them to their corresponding sections in
-    /// the response message.
-    ///
-    /// After they are added, the vectors are cleared.
-    void createResponse();
-
     /// \brief Resets any partly built response data, and internal pointers
     /// \brief Resets any partly built response data, and internal pointers
     ///
     ///
     /// Called by the QueryCleaner object upon its destruction
     /// Called by the QueryCleaner object upon its destruction
@@ -282,6 +269,12 @@ private:
         isc::auth::Query& query_;
         isc::auth::Query& query_;
     };
     };
 
 
+protected:
+    // Following methods declared protected so they can be accessed
+    // by unit tests.
+
+    void createResponse();
+
 public:
 public:
     /// Default constructor.
     /// Default constructor.
     ///
     ///
@@ -289,8 +282,8 @@ public:
     ///
     ///
     Query() :
     Query() :
         datasrc_client_(NULL), qname_(NULL), qtype_(NULL),
         datasrc_client_(NULL), qname_(NULL), qtype_(NULL),
-        response_(NULL), dnssec_(false),
-        dnssec_opt_(isc::datasrc::ZoneFinder::FIND_DEFAULT)
+        dnssec_(false), dnssec_opt_(isc::datasrc::ZoneFinder::FIND_DEFAULT),
+        response_(NULL)
     {
     {
         answers_.reserve(RESERVE_RRSETS);
         answers_.reserve(RESERVE_RRSETS);
         authorities_.reserve(RESERVE_RRSETS);
         authorities_.reserve(RESERVE_RRSETS);
@@ -402,14 +395,102 @@ public:
         {}
         {}
     };
     };
 
 
+    /// \brief Response Creator Class
+    ///
+    /// This is a helper class of Query, and is expected to be used during the
+    /// construction of the response message. This class performs the
+    /// duplicate RRset detection check.  It keeps a list of RRsets added
+    /// to the message and does not add an RRset if it is the same as one
+    /// already added.
+    ///
+    /// This class is essentially private to Query, but is visible to public
+    /// for testing purposes.  It's not expected to be used from a normal
+    /// application.
+    class ResponseCreator {
+    public:
+        /// \brief Constructor
+        ///
+        /// Reserves space for the list of RRsets.  Although the
+        /// ResponseCreator will be used to create a message from the
+        /// contents of the Query object's answers_, authorities_ and
+        /// additionals_ elements, and each of these are sized to
+        /// RESERVE_RRSETS, it is _extremely_ unlikely that all three will be
+        /// filled to capacity.  So we reserve more elements than in each of
+        /// these components, but not three times the amount.
+        ///
+        /// As with the answers_, authorities_ and additionals_ elements, the
+        /// reservation is made in the constructor to avoid dynamic allocation
+        /// of memory.  The ResponseCreator is a member variable of the Query
+        /// object so is constructed once and lasts as long as that object.
+        /// Internal state is cleared through the clear() method.
+        ResponseCreator() {
+            added_.reserve(2 * RESERVE_RRSETS);
+        }
+
+        /// \brief Reset internal state
+        void clear() {
+            added_.clear();
+        }
+
+        /// \brief Complete the response message with filling in the
+        /// response sections.
+        ///
+        /// This is the final step of the Query::process() method, and within
+        /// that method, it should be called before it returns (if any
+        /// response data is to be added)
+        ///
+        /// This will take a message to build and each RRsets for the answer,
+        /// authority, and additional sections, and add them to their
+        /// corresponding sections in the given message.  The RRsets are
+        /// filtered such that a particular RRset appears only once in the
+        /// message.
+        ///
+        /// If \c dnssec is true, it tells the message to include any RRSIGs
+        /// attached to the RRsets.
+        void create(
+            isc::dns::Message& message,
+            const std::vector<isc::dns::ConstRRsetPtr>& answers_,
+            const std::vector<isc::dns::ConstRRsetPtr>& authorities_,
+            const std::vector<isc::dns::ConstRRsetPtr>& additionals_,
+            const bool dnssec);
+
+    private:
+        // \brief RRset comparison functor.
+        struct IsSameKind : public std::binary_function<
+                            const isc::dns::AbstractRRset*,
+                            const isc::dns::AbstractRRset*,
+                            bool> {
+            bool operator()(const isc::dns::AbstractRRset* r1,
+                            const isc::dns::AbstractRRset* r2) const {
+                return (r1->isSameKind(*r2));
+            }
+        };
+
+        /// Insertion operation
+        ///
+        /// \param message Message to which the RRset is to be added
+        /// \param section Section of the message in which the RRset is put
+        /// \param rrset Pointer to RRset to be added to the message
+        /// \param dnssec Whether RRSIG records should be added as well
+        void addRRset(isc::dns::Message& message,
+                      const isc::dns::Message::Section section,
+                      const isc::dns::ConstRRsetPtr& rrset, const bool dnssec);
+
+
+    private:
+        /// List of RRsets already added to the message
+        std::vector<const isc::dns::AbstractRRset*> added_;
+    };
+
 private:
 private:
     const isc::datasrc::DataSourceClient* datasrc_client_;
     const isc::datasrc::DataSourceClient* datasrc_client_;
     const isc::dns::Name* qname_;
     const isc::dns::Name* qname_;
     const isc::dns::RRType* qtype_;
     const isc::dns::RRType* qtype_;
-    isc::dns::Message* response_;
     bool dnssec_;
     bool dnssec_;
     isc::datasrc::ZoneFinder::FindOptions dnssec_opt_;
     isc::datasrc::ZoneFinder::FindOptions dnssec_opt_;
+    ResponseCreator response_creator_;
 
 
+    isc::dns::Message* response_;
     std::vector<isc::dns::ConstRRsetPtr> answers_;
     std::vector<isc::dns::ConstRRsetPtr> answers_;
     std::vector<isc::dns::ConstRRsetPtr> authorities_;
     std::vector<isc::dns::ConstRRsetPtr> authorities_;
     std::vector<isc::dns::ConstRRsetPtr> additionals_;
     std::vector<isc::dns::ConstRRsetPtr> additionals_;

+ 143 - 1
src/bin/auth/tests/query_unittest.cc

@@ -12,12 +12,13 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include <map>
 #include <sstream>
 #include <sstream>
 #include <vector>
 #include <vector>
-#include <map>
 
 
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
+#include <boost/static_assert.hpp>
 
 
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -2367,4 +2368,145 @@ TEST_F(QueryTest, emptyNameWithNSEC3) {
     EXPECT_TRUE(result->isNSEC3Signed());
     EXPECT_TRUE(result->isNSEC3Signed());
     EXPECT_FALSE(result->isWildcard());
     EXPECT_FALSE(result->isWildcard());
 }
 }
+
+// Vector of RRsets used for the test.   Having this external to functions and
+// classes used for the testing simplifies the code.
+std::vector<RRsetPtr> rrset_vector;
+
+// Callback function for masterLoad.
+void
+loadRRsetVectorCallback(RRsetPtr rrsetptr) {
+    rrset_vector.push_back(rrsetptr);
+}
+
+// Load a set of RRsets into a vector for use in the duplicate RRset test.
+// They don't make a lot of sense as a zone, they are just different.  The
+// variables used in the stringstream input have been chosen so that each
+// represents just one RRset.
+void
+loadRRsetVector() {
+    stringstream ss;
+
+    // Comments indicate offset in the rrset_vector (when loaded) and the
+    // number of RRs in that RRset.
+    ss << soa_txt               // 0(1)
+       << zone_ns_txt           // 1(3)
+       << delegation_txt        // 2(4)
+       << delegation_ds_txt     // 3(1)
+       << mx_txt                // 4(3)
+       << www_a_txt             // 5(1)
+       << cname_txt             // 6(1)
+       << cname_nxdom_txt       // 7(1)
+       << cname_out_txt;        // 8(1)
+    rrset_vector.clear();
+    masterLoad(ss, Name("example.com."), RRClass::IN(),
+               loadRRsetVectorCallback);
+}
+
+TEST_F(QueryTest, DuplicateNameRemoval) {
+
+    // Load some RRsets into the master vector.
+    loadRRsetVector();
+    EXPECT_EQ(9, rrset_vector.size());
+
+    // Create an answer, authority and additional vector with some overlapping
+    // entries.  The following indicate which elements from rrset_vector
+    // go into each section vector.  (The values have been separated to show
+    // the overlap.)
+    //
+    // answer     = 0 1 2 3
+    // authority  =     2 3 4 5 6 7...
+    //                     ...5 (duplicate in the same section)
+    // additional = 0     3       7 8
+    //
+    // If the duplicate removal works, we should end up with the following in
+    // the message created from the three vectors:
+    //
+    // answer     = 0 1 2 3
+    // authority  =         4 5 6 7
+    // additional =                 8
+    //
+    // The expected section into which each RRset is placed is indicated in the
+    // array below.
+    const Message::Section expected_section[] = {
+        Message::SECTION_ANSWER,
+        Message::SECTION_ANSWER,
+        Message::SECTION_ANSWER,
+        Message::SECTION_ANSWER,
+        Message::SECTION_AUTHORITY,
+        Message::SECTION_AUTHORITY,
+        Message::SECTION_AUTHORITY,
+        Message::SECTION_AUTHORITY,
+        Message::SECTION_ADDITIONAL
+    };
+    EXPECT_EQ(rrset_vector.size(),
+              (sizeof(expected_section) / sizeof(Message::Section)));
+
+    // Create the vectors of RRsets (with the RRsets in a semi-random order).
+    std::vector<ConstRRsetPtr> answer;
+    answer.insert(answer.end(), rrset_vector.begin() + 2,
+                  rrset_vector.begin() + 4);
+    answer.insert(answer.end(), rrset_vector.begin() + 0,
+                  rrset_vector.begin() + 2);
+
+    std::vector<ConstRRsetPtr> authority;
+    authority.insert(authority.end(), rrset_vector.begin() + 3,
+                     rrset_vector.begin() + 8);
+    authority.push_back(rrset_vector[2]);
+    authority.push_back(rrset_vector[5]);
+
+    std::vector<ConstRRsetPtr> additional;
+    additional.insert(additional.end(), rrset_vector.begin() + 7,
+                      rrset_vector.end());
+    additional.push_back(rrset_vector[3]);
+    additional.push_back(rrset_vector[0]);
+
+    // Create the message object into which the RRsets are put
+    Message message(Message::RENDER);
+    EXPECT_EQ(0, message.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(0, message.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(0, message.getRRCount(Message::SECTION_ADDITIONAL));
+
+    // ... and fill it.
+    Query::ResponseCreator().create(message, answer, authority, additional,
+                                    false);
+
+    // Check counts in each section.  Note that these are RR counts,
+    // not RRset counts.
+    EXPECT_EQ(9, message.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(6, message.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(1, message.getRRCount(Message::SECTION_ADDITIONAL));
+
+    // ... and check that the RRsets are in the correct section
+    BOOST_STATIC_ASSERT(Message::SECTION_QUESTION == 0);
+    BOOST_STATIC_ASSERT(Message::SECTION_ANSWER == 1);
+    BOOST_STATIC_ASSERT(Message::SECTION_AUTHORITY == 2);
+    BOOST_STATIC_ASSERT(Message::SECTION_ADDITIONAL == 3);
+    Message::Section sections[] = {
+        Message::SECTION_QUESTION,
+        Message::SECTION_ANSWER,
+        Message::SECTION_AUTHORITY,
+        Message::SECTION_ADDITIONAL
+    };
+    for (int section = 1; section <= 3; ++section) {
+        for (int vecindex = 0; vecindex < rrset_vector.size(); ++vecindex) {
+            // Prepare error message in case an assertion fails (as the default
+            // message will only refer to the loop indexes).
+            stringstream ss;
+            ss << "section " << section << ", name "
+               << rrset_vector[vecindex]->getName();
+            SCOPED_TRACE(ss.str());
+
+            // Check RRset is in the right section and not in the wrong
+            // section.
+            if (sections[section] == expected_section[vecindex]) {
+                EXPECT_TRUE(message.hasRRset(sections[section],
+                            rrset_vector[vecindex]));
+            } else {
+                EXPECT_FALSE(message.hasRRset(sections[section],
+                             rrset_vector[vecindex]));
+            }
+        }
+    }
+}
 }
 }

+ 12 - 0
src/bin/bind10/bind10.xml

@@ -45,6 +45,7 @@
     <cmdsynopsis>
     <cmdsynopsis>
       <command>bind10</command>
       <command>bind10</command>
       <arg><option>-c <replaceable>config-filename</replaceable></option></arg>
       <arg><option>-c <replaceable>config-filename</replaceable></option></arg>
+      <arg><option>-i</option></arg>
       <arg><option>-m <replaceable>file</replaceable></option></arg>
       <arg><option>-m <replaceable>file</replaceable></option></arg>
       <arg><option>-n</option></arg>
       <arg><option>-n</option></arg>
       <arg><option>-p <replaceable>data_path</replaceable></option></arg>
       <arg><option>-p <replaceable>data_path</replaceable></option></arg>
@@ -56,6 +57,7 @@
       <arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
       <arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
       <arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
       <arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
       <arg><option>--no-cache</option></arg>
       <arg><option>--no-cache</option></arg>
+      <arg><option>--no-kill</option></arg>
       <arg><option>--pid-file</option> <replaceable>filename</replaceable></arg>
       <arg><option>--pid-file</option> <replaceable>filename</replaceable></arg>
       <arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
       <arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
       <arg><option>--user <replaceable>user</replaceable></option></arg>
       <arg><option>--user <replaceable>user</replaceable></option></arg>
@@ -156,6 +158,16 @@
       </varlistentry>
       </varlistentry>
 
 
       <varlistentry>
       <varlistentry>
+        <term><option>-i</option>, <option>--no-kill</option></term>
+        <listitem>
+	  <para>When this option is passed, <command>bind10</command>
+	  does not send SIGTERM and SIGKILL signals to modules during
+	  shutdown. (This option was introduced for use during
+	  testing.)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
         <term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
 <!-- TODO: example more detail. -->
 <!-- TODO: example more detail. -->
         <listitem>
         <listitem>

+ 32 - 23
src/bin/bind10/bind10_src.py.in

@@ -167,8 +167,9 @@ class BoB:
     """Boss of BIND class."""
     """Boss of BIND class."""
     
     
     def __init__(self, msgq_socket_file=None, data_path=None,
     def __init__(self, msgq_socket_file=None, data_path=None,
-    config_filename=None, clear_config=False, nocache=False, verbose=False,
-    setuid=None, username=None, cmdctl_port=None, wait_time=10):
+                 config_filename=None, clear_config=False, nocache=False,
+                 verbose=False, nokill=False, setuid=None, username=None,
+                 cmdctl_port=None, wait_time=10):
         """
         """
             Initialize the Boss of BIND. This is a singleton (only one can run).
             Initialize the Boss of BIND. This is a singleton (only one can run).
         
         
@@ -208,6 +209,7 @@ class BoB:
         self.uid = setuid
         self.uid = setuid
         self.username = username
         self.username = username
         self.verbose = verbose
         self.verbose = verbose
+        self.nokill = nokill
         self.data_path = data_path
         self.data_path = data_path
         self.config_filename = config_filename
         self.config_filename = config_filename
         self.clear_config = clear_config
         self.clear_config = clear_config
@@ -705,32 +707,36 @@ class BoB:
         # still not enough.
         # still not enough.
         time.sleep(1)
         time.sleep(1)
         self.reap_children()
         self.reap_children()
-        # next try sending a SIGTERM
-        components_to_stop = list(self.components.values())
-        for component in components_to_stop:
-            logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
-            try:
-                component.kill()
-            except OSError:
-                # ignore these (usually ESRCH because the child
-                # finally exited)
-                pass
-        # finally, send SIGKILL (unmaskable termination) until everybody dies
-        while self.components:
-            # XXX: some delay probably useful... how much is uncertain
-            time.sleep(0.1)  
-            self.reap_children()
+
+        # Send TERM and KILL signals to modules if we're not prevented
+        # from doing so
+        if not self.nokill:
+            # next try sending a SIGTERM
             components_to_stop = list(self.components.values())
             components_to_stop = list(self.components.values())
             for component in components_to_stop:
             for component in components_to_stop:
-                logger.info(BIND10_SEND_SIGKILL, component.name(),
-                            component.pid())
+                logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
                 try:
                 try:
-                    component.kill(True)
+                    component.kill()
                 except OSError:
                 except OSError:
                     # ignore these (usually ESRCH because the child
                     # ignore these (usually ESRCH because the child
                     # finally exited)
                     # finally exited)
                     pass
                     pass
-        logger.info(BIND10_SHUTDOWN_COMPLETE)
+            # finally, send SIGKILL (unmaskable termination) until everybody dies
+            while self.components:
+                # XXX: some delay probably useful... how much is uncertain
+                time.sleep(0.1)
+                self.reap_children()
+                components_to_stop = list(self.components.values())
+                for component in components_to_stop:
+                    logger.info(BIND10_SEND_SIGKILL, component.name(),
+                                component.pid())
+                    try:
+                        component.kill(True)
+                    except OSError:
+                        # ignore these (usually ESRCH because the child
+                        # finally exited)
+                        pass
+            logger.info(BIND10_SHUTDOWN_COMPLETE)
 
 
     def _get_process_exit_status(self):
     def _get_process_exit_status(self):
         return os.waitpid(-1, os.WNOHANG)
         return os.waitpid(-1, os.WNOHANG)
@@ -1046,6 +1052,8 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
                       help="UNIX domain socket file the b10-msgq daemon will use")
                       help="UNIX domain socket file the b10-msgq daemon will use")
     parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
     parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
                       default=False, help="disable hot-spot cache in authoritative DNS server")
                       default=False, help="disable hot-spot cache in authoritative DNS server")
+    parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
+                      default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
     parser.add_option("-u", "--user", dest="user", type="string", default=None,
     parser.add_option("-u", "--user", dest="user", type="string", default=None,
                       help="Change user after startup (must run as root)")
                       help="Change user after startup (must run as root)")
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
@@ -1173,8 +1181,9 @@ def main():
         # Go bob!
         # Go bob!
         boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
         boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
                            options.config_file, options.clear_config,
                            options.config_file, options.clear_config,
-                           options.nocache, options.verbose, setuid, username,
-                           options.cmdctl_port, options.wait_time)
+                           options.nocache, options.verbose, options.nokill,
+                           setuid, username, options.cmdctl_port,
+                           options.wait_time)
         startup_result = boss_of_bind.startup()
         startup_result = boss_of_bind.startup()
         if startup_result:
         if startup_result:
             logger.fatal(BIND10_STARTUP_ERROR, startup_result)
             logger.fatal(BIND10_STARTUP_ERROR, startup_result)

+ 63 - 0
src/bin/bind10/tests/bind10_test.py.in

@@ -1018,6 +1018,16 @@ class TestParseArgs(unittest.TestCase):
         options = parse_args(['--clear-config'], TestOptParser)
         options = parse_args(['--clear-config'], TestOptParser)
         self.assertEqual(True, options.clear_config)
         self.assertEqual(True, options.clear_config)
 
 
+    def test_nokill(self):
+        options = parse_args([], TestOptParser)
+        self.assertEqual(False, options.nokill)
+        options = parse_args(['--no-kill'], TestOptParser)
+        self.assertEqual(True, options.nokill)
+        options = parse_args([], TestOptParser)
+        self.assertEqual(False, options.nokill)
+        options = parse_args(['-i'], TestOptParser)
+        self.assertEqual(True, options.nokill)
+
     def test_cmdctl_port(self):
     def test_cmdctl_port(self):
         """
         """
         Test it can parse the command control port.
         Test it can parse the command control port.
@@ -1200,7 +1210,60 @@ class TestBossComponents(unittest.TestCase):
         bob.shutdown()
         bob.shutdown()
 
 
         self.assertTrue(bob.ccs.stopped)
         self.assertTrue(bob.ccs.stopped)
+
+        # Here, killed is an array where False is added if SIGTERM
+        # should be sent, or True if SIGKILL should be sent, in order in
+        # which they're sent.
         self.assertEqual([False, True], killed)
         self.assertEqual([False, True], killed)
+
+        self.assertTrue(self.__called)
+
+        bob._component_configurator.shutdown = orig
+
+    def test_nokill(self):
+        """
+        Test that the boss *doesn't* kill components which don't want to
+        stop, when asked not to (by passing the --no-kill option which
+        sets bob.nokill to True).
+        """
+        bob = MockBob()
+        bob.nokill = True
+
+        killed = []
+        class ImmortalComponent:
+            """
+            An immortal component. It does not stop when it is told so
+            (anyway it is not told so). It does not die if it is killed
+            the first time. It dies only when killed forcefully.
+            """
+            def kill(self, forceful=False):
+                killed.append(forceful)
+                if forceful:
+                    bob.components = {}
+            def pid(self):
+                return 1
+            def name(self):
+                return "Immortal"
+        bob.components = {}
+        bob.register_process(1, ImmortalComponent())
+
+        # While at it, we check the configurator shutdown is actually called
+        orig = bob._component_configurator.shutdown
+        bob._component_configurator.shutdown = self.__nullary_hook
+        self.__called = False
+
+        bob.ccs = MockModuleCCSession()
+        self.assertFalse(bob.ccs.stopped)
+
+        bob.shutdown()
+
+        self.assertTrue(bob.ccs.stopped)
+
+        # Here, killed is an array where False is added if SIGTERM
+        # should be sent, or True if SIGKILL should be sent, in order in
+        # which they're sent.
+        self.assertEqual([], killed)
+
         self.assertTrue(self.__called)
         self.assertTrue(self.__called)
 
 
         bob._component_configurator.shutdown = orig
         bob._component_configurator.shutdown = orig

+ 8 - 6
src/bin/bindctl/bindcmd.py

@@ -319,6 +319,8 @@ class BindCmdInterpreter(Cmd):
                                   param_spec = arg)
                                   param_spec = arg)
                 if ("item_default" in arg):
                 if ("item_default" in arg):
                     param.default = arg["item_default"]
                     param.default = arg["item_default"]
+                if ("item_description" in arg):
+                    param.desc = arg["item_description"]
                 cmd.add_param(param)
                 cmd.add_param(param)
             module.add_command(cmd)
             module.add_command(cmd)
         self.add_module_info(module)
         self.add_module_info(module)
@@ -361,12 +363,12 @@ class BindCmdInterpreter(Cmd):
                 if type(name) == int:
                 if type(name) == int:
                     # lump all extraneous arguments together as one big final one
                     # lump all extraneous arguments together as one big final one
                     # todo: check if last param type is a string?
                     # todo: check if last param type is a string?
-                    if (param_count > 2):
-                        while (param_count > len(command_info.params) - 1):
-                            params[param_count - 2] += params[param_count - 1]
-                            del(params[param_count - 1])
-                            param_count = len(params)
-                            cmd.params = params.copy()
+                    while (param_count > 2 and
+                           param_count > len(command_info.params) - 1):
+                        params[param_count - 2] += " " + params[param_count - 1]
+                        del(params[param_count - 1])
+                        param_count = len(params)
+                        cmd.params = params.copy()
 
 
                     # (-1, help is always in the all_params list)
                     # (-1, help is always in the all_params list)
                     if name >= len(all_params) - 1:
                     if name >= len(all_params) - 1:

+ 14 - 23
src/bin/bindctl/moduleinfo.py

@@ -57,8 +57,12 @@ class ParamInfo:
     def __str__(self):        
     def __str__(self):        
         return str("\t%s <type: %s> \t(%s)" % (self.name, self.type, self.desc))
         return str("\t%s <type: %s> \t(%s)" % (self.name, self.type, self.desc))
 
 
-    def get_name(self):
-        return "%s <type: %s>" % (self.name, self.type)
+    def get_basic_info(self):
+        if self.is_optional:
+            opt_str = "optional"
+        else:
+            opt_str = "mandatory"
+        return "%s (%s, %s)" % (self.name, self.type, opt_str)
 
 
     def get_desc(self):
     def get_desc(self):
         return self.desc
         return self.desc
@@ -155,37 +159,24 @@ class CommandInfo:
         """Prints the help info for this command to stdout"""
         """Prints the help info for this command to stdout"""
         print("Command ", self)
         print("Command ", self)
         print("\t\thelp (Get help for command)")
         print("\t\thelp (Get help for command)")
-                
+
         params = self.params.copy()
         params = self.params.copy()
         del params["help"]
         del params["help"]
 
 
         if len(params) == 0:
         if len(params) == 0:
-            print("No parameters for the command")
+            print("This command has no parameters")
             return
             return
-        
-        print("\nMandatory parameters:")
-        mandatory_infos = []
-        for info in params.values():            
-            if not info.is_optional:
-                print("    %s" % info.get_name())
-                print(textwrap.fill(info.get_desc(),
-                      initial_indent="        ",
-                      subsequent_indent="        ",
-                      width=70))
-                mandatory_infos.append(info)
 
 
-        optional_infos = [info for info in params.values() 
-                          if info not in mandatory_infos]
-        if len(optional_infos) > 0:
-            print("\nOptional parameters:")      
-            for info in optional_infos:
-                print("    %s" % info.get_name())
-                print(textwrap.fill(info.get_desc(),
+        print("Parameters:")
+        for info in params.values():
+            print("    %s" % info.get_basic_info())
+            description = info.get_desc()
+            if description != "":
+                print(textwrap.fill(description,
                       initial_indent="        ",
                       initial_indent="        ",
                       subsequent_indent="        ",
                       subsequent_indent="        ",
                       width=70))
                       width=70))
 
 
-
 class ModuleInfo:
 class ModuleInfo:
     """Define the information of one module, include module name, 
     """Define the information of one module, include module name, 
     module supporting commands.
     module supporting commands.

+ 1 - 2
src/bin/bindctl/tests/bindctl_test.py

@@ -180,12 +180,10 @@ class TestCmdSyntax(unittest.TestCase):
         self.my_assert_raise(isc.cc.data.DataTypeError, "zone set zone_name ='cn', port='cn'")
         self.my_assert_raise(isc.cc.data.DataTypeError, "zone set zone_name ='cn', port='cn'")
         self.no_assert_raise("zone reload_all")
         self.no_assert_raise("zone reload_all")
 
 
-
     def testCmdUnknownModuleSyntaxError(self):
     def testCmdUnknownModuleSyntaxError(self):
         self.my_assert_raise(CmdUnknownModuleSyntaxError, "zoned d")
         self.my_assert_raise(CmdUnknownModuleSyntaxError, "zoned d")
         self.my_assert_raise(CmdUnknownModuleSyntaxError, "dd dd  ")
         self.my_assert_raise(CmdUnknownModuleSyntaxError, "dd dd  ")
 
 
-
     def testCmdUnknownCmdSyntaxError(self):
     def testCmdUnknownCmdSyntaxError(self):
         self.my_assert_raise(CmdUnknownCmdSyntaxError, "zone dd")
         self.my_assert_raise(CmdUnknownCmdSyntaxError, "zone dd")
 
 
@@ -198,6 +196,7 @@ class TestCmdSyntax(unittest.TestCase):
     def testCmdUnknownParamSyntaxError(self):
     def testCmdUnknownParamSyntaxError(self):
         self.my_assert_raise(CmdUnknownParamSyntaxError, "zone load zone_d='cn'")
         self.my_assert_raise(CmdUnknownParamSyntaxError, "zone load zone_d='cn'")
         self.my_assert_raise(CmdUnknownParamSyntaxError, "zone reload_all zone_name = 'cn'")
         self.my_assert_raise(CmdUnknownParamSyntaxError, "zone reload_all zone_name = 'cn'")
+        self.my_assert_raise(CmdUnknownParamSyntaxError, "zone help a b c")
 
 
 class TestModuleInfo(unittest.TestCase):
 class TestModuleInfo(unittest.TestCase):
 
 

+ 15 - 30
src/bin/xfrout/b10-xfrout.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-xfrout
 .\"     Title: b10-xfrout
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: February 28. 2012
+.\"      Date: March 16. 2012
 .\"    Manual: BIND10
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"  Language: English
 .\"
 .\"
-.TH "B10\-XFROUT" "8" "February 28\&. 2012" "BIND10" "BIND10"
+.TH "B10\-XFROUT" "8" "March 16\&. 2012" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
@@ -82,26 +82,6 @@ A list of JSON objects (i\&.e\&. maps) that define per zone configuration concer
 \fBb10\-xfrout\fR\&. The supported names of each object are "origin" (the origin name of the zone), "class" (the RR class of the zone, optional, default to "IN"), and "transfer_acl" (ACL only applicable to transfer requests for that zone)\&. See the
 \fBb10\-xfrout\fR\&. The supported names of each object are "origin" (the origin name of the zone), "class" (the RR class of the zone, optional, default to "IN"), and "transfer_acl" (ACL only applicable to transfer requests for that zone)\&. See the
 BIND 10 Guide
 BIND 10 Guide
 for configuration examples\&. The default is an empty list, that is, no zone specific configuration\&.
 for configuration examples\&. The default is an empty list, that is, no zone specific configuration\&.
-.PP
-
-\fIlog_name\fR
-.PP
-
-\fIlog_file\fR
-The location of the log file if using a file channel\&. If undefined, then the file channel is closed\&. The default is
-/usr/local/var/bind10\-devel/log/Xfrout\&.log\&.
-.PP
-
-\fIlog_severity\fR
-The default is "debug"\&.
-.PP
-
-\fIlog_versions\fR
-The default is 5\&.
-.PP
-
-\fIlog_max_bytes\fR
-The default is 1048576\&.
 .if n \{\
 .if n \{\
 .sp
 .sp
 .\}
 .\}
@@ -122,19 +102,24 @@ This prototype version uses SQLite3 as its data source backend\&. Future version
 The configuration commands are:
 The configuration commands are:
 .PP
 .PP
 
 
+\fBnotify\fR
+triggers
+\fBb10\-xfrout\fR
+to send NOTIFY message(s)\&. It has the following arguments:
+\fIzone_name\fR
+to define the zone to send notifies for and the optional
+\fIzone_class\fR
+to define the class (defaults to
+\(lqIN\(rq)\&.
+\fBb10-xfrin\fR(8)
+sends this command when a zone transferred in successfully\&.
+.PP
+
 \fBshutdown\fR
 \fBshutdown\fR
 stops all outbound zone transfers and exits
 stops all outbound zone transfers and exits
 \fBb10\-xfrout\fR\&. This has an optional
 \fBb10\-xfrout\fR\&. This has an optional
 \fIpid\fR
 \fIpid\fR
 argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
 argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
-.PP
-
-\fBzone_new_data_ready\fR
-is sent from
-\fBb10-xfrin\fR(8)
-to indicate that the zone transferred in successfully\&. This triggers
-\fBb10\-xfrout\fR
-to send NOTIFY message(s)\&. This is an internal command and not exposed to the administrator\&.
 .SH "SEE ALSO"
 .SH "SEE ALSO"
 .PP
 .PP
 
 

+ 12 - 11
src/bin/xfrout/b10-xfrout.xml

@@ -20,7 +20,7 @@
 <refentry>
 <refentry>
 
 
   <refentryinfo>
   <refentryinfo>
-    <date>February 28. 2012</date>
+    <date>March 16. 2012</date>
   </refentryinfo>
   </refentryinfo>
 
 
   <refmeta>
   <refmeta>
@@ -132,6 +132,17 @@
     </para>
     </para>
 
 
     <para>
     <para>
+      <command>notify</command> triggers <command>b10-xfrout</command>
+      to send NOTIFY message(s).
+      It has the following arguments: <varname>zone_name</varname>
+      to define the zone to send notifies for and the optional
+      <varname>zone_class</varname> to define the class (defaults to
+      <quote>IN</quote>).
+      <citerefentry><refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      sends this command when a zone transferred in successfully.
+    </para>
+
+    <para>
       <command>shutdown</command> stops all outbound zone transfers
       <command>shutdown</command> stops all outbound zone transfers
       and exits <command>b10-xfrout</command>.
       and exits <command>b10-xfrout</command>.
       This has an optional <varname>pid</varname> argument to
       This has an optional <varname>pid</varname> argument to
@@ -140,16 +151,6 @@
       if configured.)
       if configured.)
     </para>
     </para>
 
 
-    <para>
-      <command>zone_new_data_ready</command> is sent from
-      <citerefentry><refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-      to indicate that the zone transferred in successfully.
-      This triggers <command>b10-xfrout</command> to send NOTIFY
-      message(s).
-      This is an internal command and not exposed to the administrator.
-<!-- not defined in spec -->
-    </para>
-
   </refsect1>
   </refsect1>
 
 
 <!--
 <!--

+ 1 - 0
src/bin/zonemgr/tests/.gitignore

@@ -1 +1,2 @@
+/initdb.file
 /zonemgr_test
 /zonemgr_test

+ 5 - 3
src/bin/zonemgr/zonemgr_messages.mes

@@ -128,9 +128,11 @@ a bug report.
 
 
 % ZONEMGR_UNKNOWN_ZONE_FAIL zone %1 (class %2) is not known to the zone manager
 % ZONEMGR_UNKNOWN_ZONE_FAIL zone %1 (class %2) is not known to the zone manager
 An XFRIN operation has failed but the zone that was the subject of the
 An XFRIN operation has failed but the zone that was the subject of the
-operation is not being managed by the zone manager.  This may indicate
-an error in the program (as the operation should not have been initiated
-if this were the case).  Please submit a bug report.
+operation is not being managed by the zone manager. This can be either the
+result of a bindctl command to transfer in a currently unknown (or mistyped)
+zone, or, if this error appears without the administrator giving transfer
+commands, it can indicate an error in the program, as it should not have
+initiated transfers of unknown zones on its own.
 
 
 % ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
 % ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
 A NOTIFY was received but the zone that was the subject of the operation
 A NOTIFY was received but the zone that was the subject of the operation

+ 2 - 2
src/lib/datasrc/Makefile.am

@@ -36,7 +36,7 @@ pkglib_LTLIBRARIES =  sqlite3_ds.la memory_ds.la
 
 
 sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
 sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
 sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
 sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
-sqlite3_ds_la_LDFLAGS = -module
+sqlite3_ds_la_LDFLAGS = -module -avoid-version
 sqlite3_ds_la_LDFLAGS += -no-undefined -version-info 1:0:0
 sqlite3_ds_la_LDFLAGS += -no-undefined -version-info 1:0:0
 sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 sqlite3_ds_la_LIBADD += libdatasrc.la
 sqlite3_ds_la_LIBADD += libdatasrc.la
@@ -44,7 +44,7 @@ sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
 
 
 memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
 memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
 memory_ds_la_SOURCES += memory_datasrc_link.cc
 memory_ds_la_SOURCES += memory_datasrc_link.cc
-memory_ds_la_LDFLAGS = -module
+memory_ds_la_LDFLAGS = -module -avoid-version
 memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 memory_ds_la_LIBADD += libdatasrc.la
 memory_ds_la_LIBADD += libdatasrc.la
 
 

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

@@ -384,7 +384,8 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
     const Name* const zonename = zoneinfo.getEnclosingZone();
     const Name* const zonename = zoneinfo.getEnclosingZone();
     if (ds == NULL) {
     if (ds == NULL) {
         task.flags |= DataSrc::NO_SUCH_ZONE;
         task.flags |= DataSrc::NO_SUCH_ZONE;
-        logger.info(DATASRC_QUERY_NO_ZONE).arg(task.qname).arg(task.qclass);
+        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_QUERY_NO_ZONE).
+            arg(task.qname).arg(task.qclass);
         return (DataSrc::SUCCESS);
         return (DataSrc::SUCCESS);
     }
     }
 
 

+ 4 - 2
src/lib/datasrc/datasrc_messages.mes

@@ -569,8 +569,10 @@ An attempt to add a NSEC3 record into the message failed, because the zone does
 not have any DS record. This indicates problem with the provided data.
 not have any DS record. This indicates problem with the provided data.
 
 
 % DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'
 % DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'
-Lookup of domain failed because the data have no zone that contain the
-domain. Maybe someone sent a query to the wrong server for some reason.
+Debug information. Lookup of domain failed because the datasource
+has no zone that contains the domain. Maybe someone sent a query
+to the wrong server for some reason. This may also happen when
+looking in the datasource for addresses for NS records.
 
 
 % DATASRC_QUERY_PROCESS processing query '%1/%2' in the '%3' class
 % DATASRC_QUERY_PROCESS processing query '%1/%2' in the '%3' class
 Debug information. A sure query is being processed now.
 Debug information. A sure query is being processed now.

+ 451 - 288
src/lib/datasrc/memory_datasrc.cc

@@ -93,6 +93,11 @@ const DomainNode::Flags WILD = DomainNode::FLAG_USER1;
 // realistic to keep this flag update for all affected nodes, and we may
 // realistic to keep this flag update for all affected nodes, and we may
 // have to reconsider the mechanism.
 // have to reconsider the mechanism.
 const DomainNode::Flags GLUE = DomainNode::FLAG_USER2;
 const DomainNode::Flags GLUE = DomainNode::FLAG_USER2;
+
+// This flag indicates the node is generated as a result of wildcard
+// expansion.  In this implementation, this flag can be set only in
+// the separate auxiliary tree of ZoneData (see the structure description).
+const DomainNode::Flags WILD_EXPANDED = DomainNode::FLAG_USER3;
 };
 };
 
 
 // Separate storage for NSEC3 RRs (and their RRSIGs).  It's an STL map
 // Separate storage for NSEC3 RRs (and their RRSIGs).  It's an STL map
@@ -121,6 +126,31 @@ struct ZoneData {
     // The main data (name + RRsets)
     // The main data (name + RRsets)
     DomainTree domains_;
     DomainTree domains_;
 
 
+    // An auxiliary tree for wildcard expanded data used in additional data
+    // processing.  It contains names like "ns.wild.example" in the following
+    // example:
+    // child.wild.example. NS ns.wild.example.
+    // *.wild.example IN AAAA 2001:db8::1234
+    // (and there's no exact ns.wild.example. in the zone).  This tree contains
+    // such names with a copy of the RRsets of the matching wildcard name
+    // with its owner name expanded, e.g.:
+    // ns.wild.example. IN AAAA 2001:db8::1234
+    // In theory, this tree could have many such wildcard-expandable names,
+    // each of which has a copy of the original list of RRsets.  In practice,
+    // however, it should be very rare that names for additional section
+    // processing are subject to wildcard expansion, so in most cases this tree
+    // should be even empty, and even if it has content it should be very
+    // small.
+private:
+    scoped_ptr<DomainTree> aux_wild_domains_;
+public:
+    DomainTree& getAuxWildDomains() {
+        if (!aux_wild_domains_) {
+            aux_wild_domains_.reset(new DomainTree);
+        }
+        return (*aux_wild_domains_);
+    }
+
     // Shortcut to the origin node, which should always exist
     // Shortcut to the origin node, which should always exist
     DomainNode* origin_data_;
     DomainNode* origin_data_;
 
 
@@ -136,9 +166,255 @@ struct ZoneData {
         const scoped_ptr<NSEC3Hash> hash_; // hash parameter/calculator
         const scoped_ptr<NSEC3Hash> hash_; // hash parameter/calculator
     };
     };
     scoped_ptr<NSEC3Data> nsec3_data_; // non NULL only when it's NSEC3 signed
     scoped_ptr<NSEC3Data> nsec3_data_; // non NULL only when it's NSEC3 signed
+
+    // This templated structure encapsulates the find result of findNode()
+    // method (also templated) below.
+    // The template parameter is expected to be either 'const DomainNode' or
+    // 'DomainNode' (to avoid misuse the template definition itself is kept
+    // private - we only expose expected typedefs).  The former is expected
+    // to be used for lookups, and the latter is expected to be used for
+    // constructing the zone.
+private:
+    template <typename NodeType>
+    struct FindNodeResultBase {
+        // Bitwise flags to represent supplemental information of the
+        // search result:
+        // Search resulted in a wildcard match.
+        static const unsigned int FIND_WILDCARD = 1;
+        // Search encountered a zone cut due to NS but continued to look for
+        // a glue.
+        static const unsigned int FIND_ZONECUT = 2;
+
+        FindNodeResultBase(ZoneFinder::Result code_param,
+                           NodeType* node_param,
+                           ConstRBNodeRRsetPtr rrset_param,
+                           unsigned int flags_param = 0) :
+            code(code_param), node(node_param), rrset(rrset_param),
+            flags(flags_param)
+        {}
+        const ZoneFinder::Result code;
+        NodeType* const node;
+        ConstRBNodeRRsetPtr const rrset;
+        const unsigned int flags;
+    };
+public:
+    typedef FindNodeResultBase<const DomainNode> FindNodeResult;
+    typedef FindNodeResultBase<DomainNode> FindMutableNodeResult;
+
+    // Identify the RBTree node that best matches the given name.
+    // See implementation notes below.
+    template <typename ResultType>
+    ResultType findNode(const Name& name,
+                        ZoneFinder::FindOptions options) const;
 };
 };
+
+/// Maintain intermediate data specific to the search context used in
+/// \c find().
+///
+/// It will be passed to \c cutCallback() (see below) and record a possible
+/// zone cut node and related RRset (normally NS or DNAME).
+struct FindState {
+    FindState(bool glue_ok) :
+        zonecut_node_(NULL),
+        dname_node_(NULL),
+        glue_ok_(glue_ok)
+    {}
+
+    // These will be set to a domain node of the highest delegation point,
+    // if any.  In fact, we could use a single variable instead of both.
+    // But then we would need to distinquish these two cases by something
+    // else and it seemed little more confusing when this was written.
+    const DomainNode* zonecut_node_;
+    const DomainNode* dname_node_;
+
+    // Delegation RRset (NS or DNAME), if found.
+    ConstRBNodeRRsetPtr rrset_;
+
+    // Whether to continue search below a delegation point.
+    // Set at construction time.
+    const bool glue_ok_;
+};
+
+// A callback called from possible zone cut nodes and nodes with DNAME.
+// This will be passed from findNode() to \c RBTree::find().
+bool cutCallback(const DomainNode& node, FindState* state) {
+    // We need to look for DNAME first, there's allowed case where
+    // DNAME and NS coexist in the apex. DNAME is the one to notice,
+    // the NS is authoritative, not delegation (corner case explicitly
+    // allowed by section 3 of 2672)
+    const Domain::const_iterator found_dname(node.getData()->find(
+                                                 RRType::DNAME()));
+    if (found_dname != node.getData()->end()) {
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_DNAME_ENCOUNTERED);
+        state->dname_node_ = &node;
+        state->rrset_ = found_dname->second;
+        // No more processing below the DNAME (RFC 2672, section 3
+        // forbids anything to exist below it, so there's no need
+        // to actually search for it). This is strictly speaking
+        // a different way than described in 4.1 of that RFC,
+        // but because of the assumption in section 3, it has the
+        // same behaviour.
+        return (true);
+    }
+
+    // Look for NS
+    const Domain::const_iterator found_ns(node.getData()->find(RRType::NS()));
+    if (found_ns != node.getData()->end()) {
+        // 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);
+        }
+
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
+
+        // BIND 9 checks if this node is not the origin.  That's probably
+        // because it can support multiple versions for dynamic updates
+        // and IXFR, and it's possible that the callback is called at
+        // the apex and the DNAME doesn't exist for a particular version.
+        // It cannot happen for us (at least for now), so we don't do
+        // that check.
+        state->zonecut_node_ = &node;
+        state->rrset_ = found_ns->second;
+
+        // Unless glue is allowed the search stops here, so we return
+        // false; otherwise return true to continue the search.
+        return (!state->glue_ok_);
+    }
+
+    // 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 notes: this method identifies an RBT node that best matches
+// the give name in terms of DNS query handling.  In many cases,
+// DomainTree::find() will result in EXACTMATCH or PARTIALMATCH (note that
+// the given name is generally expected to be contained in the zone, so
+// even if it doesn't exist, it should at least match the zone origin).
+// If it finds an exact match, that's obviously the best one.  The partial
+// match case is more complicated.
+//
+// We first need to consider the case where search hits a delegation point,
+// either due to NS or DNAME.  They are indicated as either dname_node_ or
+// zonecut_node_ being non NULL.  Usually at most one of them will be
+// something else than NULL (it might happen both are NULL, in which case we
+// consider it NOT FOUND). There's one corner case when both might be
+// something else than NULL and it is in case there's a DNAME under a zone
+// cut and we search in glue OK mode ‒ in that case we don't stop on the
+// domain with NS and ignore it for the answer, but it gets set anyway. Then
+// we find the DNAME and we need to act by it, therefore we first check for
+// DNAME and then for NS. In all other cases it doesn't matter, as at least
+// one of them is NULL.
+//
+// Next, we need to check if the RBTree search stopped at a node for a
+// subdomain of the search name (so the comparison result that stopped the
+// search is "SUPERDOMAIN"), it means the stopping node is an empty
+// non-terminal node.  In this case the search name is considered to exist
+// but no data should be found there.
+//
+// If none of above is the case, we then consider whether there's a matching
+// wildcard.  DomainTree::find() records the node if it encounters a
+// "wildcarding" node, i.e., the immediate ancestor of a wildcard name
+// (e.g., wild.example.com for *.wild.example.com), and returns it if it
+// doesn't find any node that better matches the query name.  In this case
+// we'll check if there's indeed a wildcard below the wildcarding node.
+//
+// Note, first, that the wildcard is checked after the empty
+// non-terminal domain case above, because if that one triggers, it
+// means we should not match according to 4.3.3 of RFC 1034 (the query
+// name is known to exist).
+//
+// Before we try to find a wildcard, we should check whether there's
+// an existing node that would cancel the wildcard match.  If
+// DomainTree::find() stopped at a node which has a common ancestor
+// with the query name, it might mean we are comparing with a
+// non-wildcard node. In that case, we check which part is common. If
+// we have something in common that lives below the node we got (the
+// one above *), then we should cancel the match according to section
+// 4.3.3 of RFC 1034 (as the name between the wildcard domain and the
+// query name is known to exist).
+//
+// If there's no node below the wildcarding node that shares a common ancestor
+// of the query name, we can conclude the wildcard is the best match.
+// We'll then identify the wildcard node via an incremental search.  Note that
+// there's no possibility that the query name is at an empty non terminal
+// node below the wildcarding node at this stage; that case should have been
+// caught above.
+//
+// If none of the above succeeds, we conclude the name doesn't exist in
+// the zone.
+template <typename ResultType>
+ResultType
+ZoneData::findNode(const Name& name, ZoneFinder::FindOptions options) const {
+    DomainNode* node = NULL;
+    RBTreeNodeChain<Domain> node_path;
+    FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
+
+    const DomainTree::Result result =
+        domains_.find(name, &node, node_path, cutCallback, &state);
+    const unsigned int zonecut_flag =
+        (state.zonecut_node_ != NULL) ? FindNodeResult::FIND_ZONECUT : 0;
+    if (result == DomainTree::EXACTMATCH) {
+        return (ResultType(ZoneFinder::SUCCESS, node, state.rrset_,
+                           zonecut_flag));
+    }
+    if (result == DomainTree::PARTIALMATCH) {
+        assert(node != NULL);
+        if (state.dname_node_ != NULL) { // DNAME
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
+                arg(state.rrset_->getName());
+            return (ResultType(ZoneFinder::DNAME, NULL, state.rrset_));
+        }
+        if (state.zonecut_node_ != NULL) { // DELEGATION due to NS
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
+                arg(state.rrset_->getName());
+            return (ResultType(ZoneFinder::DELEGATION, NULL, state.rrset_));
+        }
+        if (node_path.getLastComparisonResult().getRelation() ==
+            NameComparisonResult::SUPERDOMAIN) { // empty node, so NXRRSET
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).arg(name);
+            return (ResultType(ZoneFinder::NXRRSET, node,
+                               ConstRBNodeRRsetPtr()));
+        }
+        if (node->getFlag(domain_flag::WILD)) { // maybe a wildcard
+            if (node_path.getLastComparisonResult().getRelation() ==
+                NameComparisonResult::COMMONANCESTOR &&
+                node_path.getLastComparisonResult().getCommonLabels() > 1) {
+                // Wildcard canceled.  Treat it as NXDOMAIN.
+                // Note: Because the way the tree stores relative names, we
+                // will have exactly one common label (the ".") in case we have
+                // nothing common under the node we got, and we will get
+                // more common labels otherwise (yes, this relies on the
+                // internal RBTree structure, which leaks out through this
+                // little bit).
+                LOG_DEBUG(logger, DBG_TRACE_DATA,
+                          DATASRC_MEM_WILDCARD_CANCEL).arg(name);
+                return (ResultType(ZoneFinder::NXDOMAIN, NULL,
+                                   ConstRBNodeRRsetPtr()));
+            }
+            // Now the wildcard should be the best match.
+            const Name wildcard(Name("*").concatenate(
+                                    node_path.getAbsoluteName()));
+            DomainTree::Result result = domains_.find(wildcard, &node);
+            // Otherwise, why would the domain_flag::WILD be there if
+            // there was no wildcard under it?
+            assert(result == DomainTree::EXACTMATCH);
+            return (ResultType(ZoneFinder::SUCCESS, node, state.rrset_,
+                               FindNodeResult::FIND_WILDCARD |
+                               zonecut_flag));
+        }
+    }
+    // Nothing really matched.  The name may even be out-of-bailiwick.
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).arg(name);
+    return (ResultType(ZoneFinder::NXDOMAIN, node, state.rrset_));
+}
+} // unnamed namespace
+
 namespace internal {
 namespace internal {
 
 
 /// \brief An encapsulation type for a pointer of an additional node
 /// \brief An encapsulation type for a pointer of an additional node
@@ -148,7 +424,7 @@ namespace internal {
 /// in rbnode_rrset.h; this is essentially a pointer to \c DomainNode.
 /// in rbnode_rrset.h; this is essentially a pointer to \c DomainNode.
 /// In future, however, this structure may have other attributes.
 /// In future, however, this structure may have other attributes.
 struct AdditionalNodeInfo {
 struct AdditionalNodeInfo {
-    AdditionalNodeInfo(DomainNode* node) : node_(node) {}
+    explicit AdditionalNodeInfo(DomainNode* node) : node_(node) {}
     DomainNode* node_;
     DomainNode* node_;
 };
 };
 
 
@@ -310,6 +586,53 @@ RBNodeRRset::copyAdditionalNodes(RBNodeRRset& dst) const {
 } // end of internal
 } // end of internal
 
 
 namespace {
 namespace {
+/*
+ * Prepares a rrset to be return as a result.
+ *
+ * If rename is false, it returns the one provided. If it is true, it
+ * creates a new rrset with the same data but with provided name.
+ * In addition, if DNSSEC records are required by the original caller of
+ * find(), it also creates expanded RRSIG based on the RRSIG of the
+ * wildcard RRset.
+ * It is designed for wildcard case, where we create the rrsets
+ * dynamically.
+ */
+ConstRBNodeRRsetPtr
+prepareRRset(const Name& name, const ConstRBNodeRRsetPtr& rrset, bool rename,
+             ZoneFinder::FindOptions options)
+{
+    if (rename) {
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
+            arg(rrset->getName()).arg(name);
+        RRsetPtr result_base(new RRset(name, rrset->getClass(),
+                                       rrset->getType(), rrset->getTTL()));
+        for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
+             i->next()) {
+            result_base->addRdata(i->getCurrent());
+        }
+        if ((options & ZoneFinder::FIND_DNSSEC) != 0) {
+            ConstRRsetPtr sig_rrset = rrset->getRRsig();
+            if (sig_rrset) {
+                RRsetPtr result_sig(new RRset(name, sig_rrset->getClass(),
+                                              RRType::RRSIG(),
+                                              sig_rrset->getTTL()));
+                for (RdataIteratorPtr i(sig_rrset->getRdataIterator());
+                     !i->isLast();
+                     i->next())
+                {
+                    result_sig->addRdata(i->getCurrent());
+                }
+                result_base->addRRsig(result_sig);
+            }
+        }
+        RBNodeRRsetPtr result(new RBNodeRRset(result_base));
+        rrset->copyAdditionalNodes(*result);
+        return (result);
+    } else {
+        return (rrset);
+    }
+}
+
 // Specialized version of ZoneFinder::ResultContext, which specifically
 // Specialized version of ZoneFinder::ResultContext, which specifically
 // holds rrset in the form of RBNodeRRset.
 // holds rrset in the form of RBNodeRRset.
 struct RBNodeResultContext {
 struct RBNodeResultContext {
@@ -393,12 +716,22 @@ private:
             if (!glue_ok && additional.node_->getFlag(domain_flag::GLUE)) {
             if (!glue_ok && additional.node_->getFlag(domain_flag::GLUE)) {
                 continue;
                 continue;
             }
             }
+            const bool wild_expanded =
+                additional.node_->getFlag(domain_flag::WILD_EXPANDED);
             BOOST_FOREACH(const RRType& rrtype, requested_types) {
             BOOST_FOREACH(const RRType& rrtype, requested_types) {
                 Domain::const_iterator found =
                 Domain::const_iterator found =
                     additional.node_->getData()->find(rrtype);
                     additional.node_->getData()->find(rrtype);
                 if (found != additional.node_->getData()->end()) {
                 if (found != additional.node_->getData()->end()) {
-                    // TODO: wildcard consideration
-                    result.push_back(found->second);
+                    // If the additional node was generated as a result of
+                    // wildcard expansion, we return the underlying RRset,
+                    // in case the caller has the same RRset but as a result
+                    // of normal find() and needs to know they are of the same
+                    // kind; otherwise we simply use the stored RBNodeRRset.
+                    if (wild_expanded) {
+                        result.push_back(found->second->getUnderlyingRRset());
+                    } else {
+                        result.push_back(found->second);
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -838,129 +1171,6 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         }
         }
     }
     }
 
 
-    // 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),
-            dname_node_(NULL),
-            options_(options)
-        {}
-        const DomainNode* zonecut_node_;
-        const DomainNode* dname_node_;
-        ConstRBNodeRRsetPtr rrset_;
-        const FindOptions options_;
-    };
-
-    // A callback called from possible zone cut nodes and nodes with DNAME.
-    // This will be passed from the \c find() method to \c RBTree::find().
-    static bool cutCallback(const DomainNode& node, FindState* state) {
-        // We need to look for DNAME first, there's allowed case where
-        // DNAME and NS coexist in the apex. DNAME is the one to notice,
-        // the NS is authoritative, not delegation (corner case explicitly
-        // allowed by section 3 of 2672)
-        const Domain::const_iterator foundDNAME(node.getData()->find(
-            RRType::DNAME()));
-        if (foundDNAME != node.getData()->end()) {
-            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
-                      DATASRC_MEM_DNAME_ENCOUNTERED);
-            state->dname_node_ = &node;
-            state->rrset_ = foundDNAME->second;
-            // No more processing below the DNAME (RFC 2672, section 3
-            // forbids anything to exist below it, so there's no need
-            // to actually search for it). This is strictly speaking
-            // a different way than described in 4.1 of that RFC,
-            // but because of the assumption in section 3, it has the
-            // same behaviour.
-            return (true);
-        }
-
-        // Look for NS
-        const Domain::const_iterator foundNS(node.getData()->find(
-            RRType::NS()));
-        if (foundNS != node.getData()->end()) {
-            // 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);
-            }
-
-            LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
-
-            // BIND 9 checks if this node is not the origin.  That's probably
-            // because it can support multiple versions for dynamic updates
-            // and IXFR, and it's possible that the callback is called at
-            // the apex and the DNAME doesn't exist for a particular version.
-            // It cannot happen for us (at least for now), so we don't do
-            // that check.
-            state->zonecut_node_ = &node;
-            state->rrset_ = foundNS->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);
-    }
-
-    /*
-     * Prepares a rrset to be return as a result.
-     *
-     * If rename is false, it returns the one provided. If it is true, it
-     * creates a new rrset with the same data but with provided name.
-     * In addition, if DNSSEC records are required by the original caller of
-     * find(), it also creates expanded RRSIG based on the RRSIG of the
-     * wildcard RRset.
-     * It is designed for wildcard case, where we create the rrsets
-     * dynamically.
-     */
-    static ConstRBNodeRRsetPtr prepareRRset(const Name& name,
-                                            const ConstRBNodeRRsetPtr& rrset,
-                                            bool rename, FindOptions options)
-    {
-        if (rename) {
-            LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
-                arg(rrset->getName()).arg(name);
-            RRsetPtr result_base(new RRset(name, rrset->getClass(),
-                                           rrset->getType(),
-                                           rrset->getTTL()));
-            for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
-                 i->next()) {
-                result_base->addRdata(i->getCurrent());
-            }
-            if ((options & FIND_DNSSEC) != 0) {
-                ConstRRsetPtr sig_rrset = rrset->getRRsig();
-                if (sig_rrset) {
-                    RRsetPtr result_sig(new RRset(name, sig_rrset->getClass(),
-                                                  RRType::RRSIG(),
-                                                  sig_rrset->getTTL()));
-                    for (RdataIteratorPtr i(sig_rrset->getRdataIterator());
-                         !i->isLast();
-                         i->next())
-                    {
-                        result_sig->addRdata(i->getCurrent());
-                    }
-                    result_base->addRRsig(result_sig);
-                }
-            }
-            RBNodeRRsetPtr result(new RBNodeRRset(result_base));
-            rrset->copyAdditionalNodes(*result);
-            return (result);
-        } else {
-            return (rrset);
-        }
-    }
-
     // Set up FindContext object as a return value of find(), taking into
     // Set up FindContext object as a return value of find(), taking into
     // account wildcard matches and DNSSEC information.  We set the NSEC/NSEC3
     // account wildcard matches and DNSSEC information.  We set the NSEC/NSEC3
     // flag when applicable regardless of the find option; the caller would
     // flag when applicable regardless of the find option; the caller would
@@ -991,130 +1201,20 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
     {
     {
         LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FIND).arg(name).
         LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FIND).arg(name).
             arg(type);
             arg(type);
-        // Get the node
-        DomainNode* node(NULL);
-        FindState state(options);
-        RBTreeNodeChain<Domain> node_path;
-        bool rename(false);
-        switch (zone_data_->domains_.find(name, &node, node_path, cutCallback,
-                                          &state)) {
-            case DomainTree::PARTIALMATCH:
-                /*
-                 * In fact, we could use a single variable instead of
-                 * dname_node_ and zonecut_node_. But then we would need
-                 * to distinquish these two cases by something else and
-                 * it seemed little more confusing to me when I wrote it.
-                 *
-                 * Usually at most one of them will be something else than
-                 * NULL (it might happen both are NULL, in which case we
-                 * consider it NOT FOUND). There's one corner case when
-                 * both might be something else than NULL and it is in case
-                 * there's a DNAME under a zone cut and we search in
-                 * glue OK mode ‒ in that case we don't stop on the domain
-                 * with NS and ignore it for the answer, but it gets set
-                 * anyway. Then we find the DNAME and we need to act by it,
-                 * therefore we first check for DNAME and then for NS. In
-                 * all other cases it doesn't matter, as at least one of them
-                 * is NULL.
-                 */
-                if (state.dname_node_ != NULL) {
-                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
-                        arg(state.rrset_->getName());
-                    // We were traversing a DNAME node (and wanted to go
-                    // lower below it), so return the DNAME
-                    return (createFindResult(DNAME,
-                                             prepareRRset(name, state.rrset_,
-                                                          false, options)));
-                }
-                if (state.zonecut_node_ != NULL) {
-                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
-                        arg(state.rrset_->getName());
-                    return (createFindResult(DELEGATION,
-                                             prepareRRset(name, state.rrset_,
-                                                          false, options)));
-                }
-
-                // If the RBTree search stopped at a node for a super domain
-                // of the search name, it means the search name exists in
-                // the zone but is empty.  Treat it as NXRRSET.
-                if (node_path.getLastComparisonResult().getRelation() ==
-                    NameComparisonResult::SUPERDOMAIN) {
-                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).
-                        arg(name);
-                    return (createFindResult(NXRRSET, ConstRBNodeRRsetPtr()));
-                }
-
-                /*
-                 * No redirection anywhere. Let's try if it is a wildcard.
-                 *
-                 * The wildcard is checked after the empty non-terminal domain
-                 * case above, because if that one triggers, it means we should
-                 * not match according to 4.3.3 of RFC 1034 (the query name
-                 * is known to exist).
-                 */
-                if (node->getFlag(domain_flag::WILD)) {
-                    /* Should we cancel this match?
-                     *
-                     * If we compare with some node and get a common ancestor,
-                     * it might mean we are comparing with a non-wildcard node.
-                     * In that case, we check which part is common. If we have
-                     * something in common that lives below the node we got
-                     * (the one above *), then we should cancel the match
-                     * according to section 4.3.3 of RFC 1034 (as the name
-                     * between the wildcard domain and the query name is known
-                     * to exist).
-                     *
-                     * Because the way the tree stores relative names, we will
-                     * have exactly one common label (the ".") in case we have
-                     * nothing common under the node we got and we will get
-                     * more common labels otherwise (yes, this relies on the
-                     * internal RBTree structure, which leaks out through this
-                     * little bit).
-                     *
-                     * If the empty non-terminal node actually exists in the
-                     * tree, then this cancellation is not needed, because we
-                     * will not get here at all.
-                     */
-                    if (node_path.getLastComparisonResult().getRelation() ==
-                        NameComparisonResult::COMMONANCESTOR && node_path.
-                        getLastComparisonResult().getCommonLabels() > 1) {
-                        LOG_DEBUG(logger, DBG_TRACE_DATA,
-                                     DATASRC_MEM_WILDCARD_CANCEL).arg(name);
-                        return (createFindResult(NXDOMAIN,
-                                                 ConstRBNodeRRsetPtr(),
-                                                 false));
-                    }
-                    const Name wildcard(Name("*").concatenate(
-                        node_path.getAbsoluteName()));
-                    DomainTree::Result result =
-                        zone_data_->domains_.find(wildcard, &node);
-                    /*
-                     * Otherwise, why would the domain_flag::WILD be there if
-                     * there was no wildcard under it?
-                     */
-                    assert(result == DomainTree::EXACTMATCH);
-                    /*
-                     * We have the wildcard node now. Jump below the switch,
-                     * where handling of the common (exact-match) case is.
-                     *
-                     * However, rename it to the searched name.
-                     */
-                    rename = true;
-                    break;
-                }
 
 
-                // fall through
-            case DomainTree::NOTFOUND:
-                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).
-                    arg(name);
-                return (createFindResult(NXDOMAIN, ConstRBNodeRRsetPtr(),
-                                         false));
-            case DomainTree::EXACTMATCH: // This one is OK, handle it
-                break;
-            default:
-                assert(0);
+        // Get the node.  All other cases than an exact match are handled
+        // in findNode().  We simply construct a result structure and return.
+        const ZoneData::FindNodeResult node_result =
+            zone_data_->findNode<ZoneData::FindNodeResult>(name, options);
+        if (node_result.code != SUCCESS) {
+            return (createFindResult(node_result.code, node_result.rrset));
         }
         }
+
+        // We've found an exact match, may or may not be a result of wildcard.
+        const DomainNode* node = node_result.node;
         assert(node != NULL);
         assert(node != NULL);
+        const bool rename = ((node_result.flags &
+                              ZoneData::FindNodeResult::FIND_WILDCARD) != 0);
 
 
         // If there is an exact match but the node is empty, it's equivalent
         // If there is an exact match but the node is empty, it's equivalent
         // to NXRRSET.
         // to NXRRSET.
@@ -1184,7 +1284,8 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
     }
     }
 };
 };
 
 
-InMemoryZoneFinder::InMemoryZoneFinder(const RRClass& zone_class, const Name& origin) :
+InMemoryZoneFinder::InMemoryZoneFinder(const RRClass& zone_class,
+                                       const Name& origin) :
     impl_(new InMemoryZoneFinderImpl(zone_class, origin))
     impl_(new InMemoryZoneFinderImpl(zone_class, origin))
 {
 {
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_CREATE).arg(origin).
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_CREATE).arg(origin).
@@ -1323,28 +1424,24 @@ getAdditionalName(RRType rrtype, const rdata::Rdata& rdata) {
     }
     }
 }
 }
 
 
-bool
-checkZoneCut(const DomainNode& node, pair<bool, bool>* arg) {
-    // We are only interested in the highest zone cut information.
-    // Ignore others and continue the search.
-    if (arg->first) {
-        return (false);
-    }
-    // Once we encounter a delegation point due to a DNAME, anything under it
-    // should be hidden.
-    if (node.getData()->find(RRType::DNAME()) != node.getData()->end()) {
-        return (true);
-    } else if (node.getData()->find(RRType::NS()) != node.getData()->end()) {
-        arg->first = true;
-        arg->second = true;
-        return (false);
-    }
-    return (false);
+void
+convertAndInsert(const DomainPair& rrset_item, DomainPtr dst_domain,
+                 const Name* dstname)
+{
+    // We copy RRSIGs, too, if they are attached in case we need it in
+    // getAdditional().
+    dst_domain->insert(DomainPair(rrset_item.first,
+                                  prepareRRset(*dstname, rrset_item.second,
+                                               true,
+                                               ZoneFinder::FIND_DNSSEC)));
 }
 }
 
 
 void
 void
-addAdditional(RBNodeRRset* rrset, ZoneData* zone_data) {
+addAdditional(RBNodeRRset* rrset, ZoneData* zone_data,
+              vector<RBNodeRRset*>* wild_rrsets)
+{
     RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
     RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
+    bool match_wild = false;    // will be true if wildcard match is found
     for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
     for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
         // For each domain name that requires additional section processing
         // For each domain name that requires additional section processing
         // in each RDATA, search the tree for the name and remember it if
         // in each RDATA, search the tree for the name and remember it if
@@ -1352,31 +1449,91 @@ addAdditional(RBNodeRRset* rrset, ZoneData* zone_data) {
         // child zone), mark the node as "GLUE", so we can selectively
         // child zone), mark the node as "GLUE", so we can selectively
         // include/exclude them when we use it.
         // include/exclude them when we use it.
 
 
-        // TODO: wildcard
-        RBTreeNodeChain<Domain> node_path;
-        DomainNode* node = NULL;
-        // The callback argument is a pair of bools: the first is a flag to
-        // only check the highest cut; the second one records whether the
-        // search goes under a zone cut.
-        pair<bool, bool> callback_arg(false, false);
-        const DomainTree::Result result =
-            zone_data->domains_.find(
-                getAdditionalName(rrset->getType(),
-                                  rdata_iterator->getCurrent()),
-                &node, node_path, checkZoneCut, &callback_arg);
-        if (result == DomainTree::EXACTMATCH) {
-            assert(node != NULL);
-            if (callback_arg.second ||
-                (node->getFlag(DomainNode::FLAG_CALLBACK) &&
-                 node->getData()->find(RRType::NS()) !=
-                 node->getData()->end())) {
-                // The node is under or at a zone cut; mark it as a glue.
-                node->setFlag(domain_flag::GLUE);
+        const Name& name = getAdditionalName(rrset->getType(),
+                                             rdata_iterator->getCurrent());
+        const ZoneData::FindMutableNodeResult result =
+            zone_data->findNode<ZoneData::FindMutableNodeResult>(
+                name, ZoneFinder::FIND_GLUE_OK);
+        if (result.code != ZoneFinder::SUCCESS) {
+            // We are not interested in anything but a successful match.
+            continue;
+        }
+        DomainNode* node = result.node;
+        assert(node != NULL);
+        if ((result.flags & ZoneData::FindNodeResult::FIND_ZONECUT) != 0 ||
+            (node->getFlag(DomainNode::FLAG_CALLBACK) &&
+             node->getData()->find(RRType::NS()) != node->getData()->end())) {
+            // The node is under or at a zone cut; mark it as a glue.
+            node->setFlag(domain_flag::GLUE);
+        }
+
+        // A rare case: the additional name may have to be expanded with a
+        // wildcard.  We'll store the name in a separate auxiliary tree,
+        // copying all RRsets of the original wildcard node with expanding
+        // the owner name.  This is costly in terms of memory, but this case
+        // should be pretty rare.  On the other hand we won't have to worry
+        // about wildcard expansion in getAdditional, which is quite
+        // performance sensitive.
+        DomainNode* wildnode = NULL;
+        if ((result.flags & ZoneData::FindNodeResult::FIND_WILDCARD) != 0) {
+            // Wildcard and glue shouldn't coexist.  Make it sure here.
+            assert(!node->getFlag(domain_flag::GLUE));
+
+            if (zone_data->getAuxWildDomains().insert(name, &wildnode)
+                == DomainTree::SUCCESS) {
+                // If we first insert the node, copy the RRsets.  If the
+                // original node was empty, we add empty data so
+                // addWildAdditional() can get an exactmatch for this name.
+                DomainPtr dst_domain(new Domain);
+                if (!node->isEmpty()) {
+                    for_each(node->getData()->begin(), node->getData()->end(),
+                             boost::bind(convertAndInsert, _1, dst_domain,
+                                         &name));
+                }
+                wildnode->setData(dst_domain);
+                // Mark the node as "wildcard expanded" so it can be
+                // distinguished at lookup time.
+                wildnode->setFlag(domain_flag::WILD_EXPANDED);
             }
             }
+            match_wild = true;
+            node = wildnode;
+        }
+
+        // If this name wasn't subject to wildcard substitution, we can add
+        // the additional information to the RRset now; otherwise I'll defer
+        // it until the entire auxiliary tree is built (pointers may be
+        // invalidated as we build it).
+        if (wildnode == NULL) {
             // Note that node may be empty.  We should keep it in the list
             // Note that node may be empty.  We should keep it in the list
             // in case we dynamically update the tree and it becomes non empty
             // in case we dynamically update the tree and it becomes non empty
             // (which is not supported yet)
             // (which is not supported yet)
-            rrset->addAdditionalNode(node);
+            rrset->addAdditionalNode(AdditionalNodeInfo(node));
+        }
+    }
+
+    if (match_wild) {
+        wild_rrsets->push_back(rrset);
+    }
+}
+
+void
+addWildAdditional(RBNodeRRset* rrset, ZoneData* zone_data) {
+    // Similar to addAdditional(), but due to the first stage we know that
+    // the rrset should contain a name stored in the auxiliary trees, and
+    // that it should be found as an exact match.  The RRset may have other
+    // names that didn't require wildcard expansion, but we can simply ignore
+    // them in this context.  (Note that if we find an exact match in the
+    // auxiliary tree, it shouldn't be in the original zone; otherwise it
+    // shouldn't have resulted in wildcard in the first place).
+
+    RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
+    for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
+        const Name& name = getAdditionalName(rrset->getType(),
+                                             rdata_iterator->getCurrent());
+        DomainNode* wildnode = NULL;
+        if (zone_data->getAuxWildDomains().find(name, &wildnode) ==
+            DomainTree::EXACTMATCH) {
+            rrset->addAdditionalNode(AdditionalNodeInfo(wildnode));
         }
         }
     }
     }
 }
 }
@@ -1398,8 +1555,14 @@ InMemoryZoneFinder::load(const string& filename) {
 
 
     // For each RRset in need_additionals, identify the corresponding
     // For each RRset in need_additionals, identify the corresponding
     // RBnode for additional processing and associate it in the RRset.
     // RBnode for additional processing and associate it in the RRset.
+    // If some additional names in an RRset RDATA as additional need wildcard
+    // expansion, we'll remember them in a separate vector, and handle them
+    // with addWildAdditional.
+    vector<RBNodeRRset*> wild_additionals;
     for_each(need_additionals.begin(), need_additionals.end(),
     for_each(need_additionals.begin(), need_additionals.end(),
-             boost::bind(addAdditional, _1, tmp.get()));
+             boost::bind(addAdditional, _1, tmp.get(), &wild_additionals));
+    for_each(wild_additionals.begin(), wild_additionals.end(),
+             boost::bind(addWildAdditional, _1, tmp.get()));
 
 
     // If the zone is NSEC3-signed, check if it has NSEC3PARAM
     // If the zone is NSEC3-signed, check if it has NSEC3PARAM
     if (tmp->nsec3_data_) {
     if (tmp->nsec3_data_) {

+ 7 - 2
src/lib/datasrc/rbtree.h

@@ -124,7 +124,8 @@ public:
     enum Flags {
     enum Flags {
         FLAG_CALLBACK = 1, ///< Callback enabled. See \ref callback
         FLAG_CALLBACK = 1, ///< Callback enabled. See \ref callback
         FLAG_USER1 = 0x80000000U, ///< Application specific flag
         FLAG_USER1 = 0x80000000U, ///< Application specific flag
-        FLAG_USER2 = 0x40000000U  ///< Application specific flag
+        FLAG_USER2 = 0x40000000U, ///< Application specific flag
+        FLAG_USER3 = 0x20000000U  ///< Application specific flag
     };
     };
 private:
 private:
     // Some flag values are expected to be used for internal purposes
     // Some flag values are expected to be used for internal purposes
@@ -133,7 +134,7 @@ private:
     // explicitly defined in \c Flags.  This constant represents all
     // explicitly defined in \c Flags.  This constant represents all
     // such flags.
     // such flags.
     static const uint32_t SETTABLE_FLAGS = (FLAG_CALLBACK | FLAG_USER1 |
     static const uint32_t SETTABLE_FLAGS = (FLAG_CALLBACK | FLAG_USER1 |
-                                            FLAG_USER2);
+                                            FLAG_USER2 | FLAG_USER3);
 
 
 public:
 public:
 
 
@@ -1161,6 +1162,10 @@ RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
 }
 }
 
 
 
 
+// Note: when we redesign this (still keeping the basic concept), we should
+// change this part so the newly created node will be used for the inserted
+// name (and therefore the name for the existing node doesn't change).
+// Otherwise, things like shortcut links between nodes won't work.
 template <typename T>
 template <typename T>
 void
 void
 RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
 RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {

+ 4 - 3
src/lib/datasrc/tests/rbnode_rrset_unittest.cc

@@ -12,16 +12,17 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <stdexcept>
-
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 #include <datasrc/rbnode_rrset.h>
 #include <datasrc/rbnode_rrset.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/dnsmessage_test.h>
 
 
+#include <dns/tests/unittest_util.h>
+
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
-#include <dns/tests/unittest_util.h>
+#include <sstream>
+#include <stdexcept>
 
 
 using isc::UnitTestUtil;
 using isc::UnitTestUtil;
 
 

+ 8 - 2
src/lib/datasrc/tests/testdata/contexttest.zone

@@ -1,7 +1,7 @@
 ;; test zone file used for ZoneFinderContext tests.
 ;; test zone file used for ZoneFinderContext tests.
 ;; RRSIGs are (obviouslly) faked ones for testing.
 ;; RRSIGs are (obviouslly) faked ones for testing.
 
 
-example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 56 3600 300 3600000 3600
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 67 3600 300 3600000 3600
 example.org.			      3600 IN NS	ns1.example.org.
 example.org.			      3600 IN NS	ns1.example.org.
 example.org.			      3600 IN NS	ns2.example.org.
 example.org.			      3600 IN NS	ns2.example.org.
 example.org.			      3600 IN MX	1 mx1.example.org.
 example.org.			      3600 IN MX	1 mx1.example.org.
@@ -57,9 +57,15 @@ d.example.org. 	      	      3600 IN NS	ns1.example.org.
 foo.ns.empty.example.org.     3600 IN A		192.0.2.13
 foo.ns.empty.example.org.     3600 IN A		192.0.2.13
 bar.ns.empty.example.org.     3600 IN A		192.0.2.14
 bar.ns.empty.example.org.     3600 IN A		192.0.2.14
 
 
-;; delegation; the NS name matches a wildcard (and there's no exact match)
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match).  One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
 e.example.org. 	      	      3600 IN NS	ns.wild.example.org.
 e.example.org. 	      	      3600 IN NS	ns.wild.example.org.
+e.example.org. 	      	      3600 IN NS	ns.emptywild.example.org.
+e.example.org. 	      	      3600 IN NS	ns2.example.org.
 *.wild.example.org.	      3600 IN A		192.0.2.15
 *.wild.example.org.	      3600 IN A		192.0.2.15
+a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
 
 
 ;; additional for an answer RRset (MX) as a result of wildcard
 ;; additional for an answer RRset (MX) as a result of wildcard
 ;; expansion
 ;; expansion

+ 17 - 7
src/lib/datasrc/tests/zone_finder_context_unittest.cc

@@ -262,18 +262,28 @@ TEST_P(ZoneFinderContextTest, getAdditionalDelegationWithEmptyName) {
 }
 }
 
 
 TEST_P(ZoneFinderContextTest, getAdditionalDelegationWithWild) {
 TEST_P(ZoneFinderContextTest, getAdditionalDelegationWithWild) {
-    // The NS name needs to be expanded by a wildcard.  Currently it doesn't
-    // work for the optimized in-memory version.
-    if (GetParam() == createInMemoryClient) {
-        return;
-    }
-
+    // An NS name needs to be expanded by a wildcard.  Another NS name
+    // also matches a wildcard, but it's an empty node, so there's no
+    // corresponding additional RR.  The other NS name isn't subject to
+    // wildcard expansion, which shouldn't cause any disruption.
     ZoneFinderContextPtr ctx = finder_->find(Name("www.e.example.org"),
     ZoneFinderContextPtr ctx = finder_->find(Name("www.e.example.org"),
                                              RRType::AAAA());
                                              RRType::AAAA());
     EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
     EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
     ctx->getAdditional(REQUESTED_BOTH, result_sets_);
     ctx->getAdditional(REQUESTED_BOTH, result_sets_);
-    rrsetsCheck("ns.wild.example.org. 3600 IN A 192.0.2.15\n",
+    rrsetsCheck("ns.wild.example.org. 3600 IN A 192.0.2.15\n"
+                "ns2.example.org. 3600 IN A 192.0.2.2\n",
                 result_sets_.begin(), result_sets_.end());
                 result_sets_.begin(), result_sets_.end());
+
+    // ns.wild.example.org/A (expanded from a wildcard) should be considered
+    // the same kind, whether it's a direct result of find() or a result of
+    // getAdditional().
+    ctx = finder_->find(Name("ns.wild.example.org"), RRType::A());
+    EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+    for (vector<ConstRRsetPtr>::const_iterator it = result_sets_.begin();
+         it != result_sets_.end(); ++it) {
+        const bool same_kind = (*it)->isSameKind(*ctx->rrset);
+        EXPECT_EQ((*it)->getName() == ctx->rrset->getName(), same_kind);
+    }
 }
 }
 
 
 TEST_P(ZoneFinderContextTest, getAdditionalDelegationForWild) {
 TEST_P(ZoneFinderContextTest, getAdditionalDelegationForWild) {

+ 1 - 1
src/lib/dns/python/Makefile.am

@@ -46,7 +46,7 @@ EXTRA_DIST += nsec3hash_python_inc.cc
 
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # suffix for dynamic objects.  -module is necessary to work this around.
-pydnspp_la_LDFLAGS += -module
+pydnspp_la_LDFLAGS += -module -avoid-version
 pydnspp_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
 pydnspp_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
 pydnspp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 pydnspp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 pydnspp_la_LIBADD += libpydnspp.la
 pydnspp_la_LIBADD += libpydnspp.la

+ 1 - 1
src/lib/dns/rrset.h

@@ -482,8 +482,8 @@ public:
     /// \param other Pointer to another AbstractRRset to compare
     /// \param other Pointer to another AbstractRRset to compare
     ///              against.
     ///              against.
     virtual bool isSameKind(const AbstractRRset& other) const;
     virtual bool isSameKind(const AbstractRRset& other) const;
-
     //@}
     //@}
+
 };
 };
 
 
 /// \brief The \c RdataIterator class is an abstract base class that
 /// \brief The \c RdataIterator class is an abstract base class that

+ 4 - 3
src/lib/dns/tests/rrset_unittest.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <stdexcept>
-
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
 #include <dns/name.h>
 #include <dns/name.h>
@@ -24,9 +22,12 @@
 #include <dns/rrttl.h>
 #include <dns/rrttl.h>
 #include <dns/rrset.h>
 #include <dns/rrset.h>
 
 
+#include <dns/tests/unittest_util.h>
+
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
-#include <dns/tests/unittest_util.h>
+#include <stdexcept>
+#include <sstream>
 
 
 using isc::UnitTestUtil;
 using isc::UnitTestUtil;
 
 

+ 2 - 2
src/lib/log/logger_manager_impl.cc

@@ -115,8 +115,8 @@ void
 LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
 LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
                                          const OutputOption& opt)
                                          const OutputOption& opt)
 {
 {
-    LOG4CPLUS_OPEN_MODE_TYPE mode = 
-        LOG4CPLUS_FSTREAM_NAMESPACE::ios::app;  // Append to existing file
+    // Append to existing file
+    std::ios::openmode mode = std::ios::app;
 
 
     log4cplus::SharedAppenderPtr fileapp;
     log4cplus::SharedAppenderPtr fileapp;
     if (opt.maxsize == 0) {
     if (opt.maxsize == 0) {

+ 2 - 2
src/lib/python/isc/acl/Makefile.am

@@ -26,11 +26,11 @@ _dns_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # suffix for dynamic objects.  -module is necessary to work this around.
-acl_la_LDFLAGS += -module
+acl_la_LDFLAGS += -module -avoid-version
 acl_la_LIBADD = $(top_builddir)/src/lib/acl/libacl.la
 acl_la_LIBADD = $(top_builddir)/src/lib/acl/libacl.la
 acl_la_LIBADD += $(PYTHON_LIB)
 acl_la_LIBADD += $(PYTHON_LIB)
 
 
-_dns_la_LDFLAGS += -module
+_dns_la_LDFLAGS += -module -avoid-version
 _dns_la_LIBADD = $(top_builddir)/src/lib/acl/libdnsacl.la
 _dns_la_LIBADD = $(top_builddir)/src/lib/acl/libdnsacl.la
 _dns_la_LIBADD += $(PYTHON_LIB)
 _dns_la_LIBADD += $(PYTHON_LIB)
 
 

+ 17 - 8
src/lib/python/isc/cc/session.py

@@ -72,7 +72,7 @@ class Session:
         self._lname = None
         self._lname = None
         self._closed = True
         self._closed = True
 
 
-    def sendmsg(self, env, msg = None):
+    def sendmsg(self, env, msg=None):
         with self._lock:
         with self._lock:
             if self._closed:
             if self._closed:
                 raise SessionError("Session has been closed.")
                 raise SessionError("Session has been closed.")
@@ -82,15 +82,24 @@ class Session:
                 raise ProtocolError("Envelope too large")
                 raise ProtocolError("Envelope too large")
             if type(msg) == dict:
             if type(msg) == dict:
                 msg = isc.cc.message.to_wire(msg)
                 msg = isc.cc.message.to_wire(msg)
-            self._socket.setblocking(1)
             length = 2 + len(env);
             length = 2 + len(env);
-            if msg:
+            if msg is not None:
                 length += len(msg)
                 length += len(msg)
-            self._socket.send(struct.pack("!I", length))
-            self._socket.send(struct.pack("!H", len(env)))
-            self._socket.send(env)
-            if msg:
-                self._socket.send(msg)
+
+            # Build entire message.
+            data = struct.pack("!I", length)
+            data += struct.pack("!H", len(env))
+            data += env
+            if msg is not None:
+                data += msg
+
+            # Send it in the blocking mode.  On some systems send() may
+            # actually send only part of the data, so we need to repeat it
+            # until all data have been sent out.
+            self._socket.setblocking(1)
+            while len(data) > 0:
+                cc = self._socket.send(data)
+                data = data[cc:]
 
 
     def recvmsg(self, nonblock = True, seq = None):
     def recvmsg(self, nonblock = True, seq = None):
         """Reads a message. If nonblock is true, and there is no
         """Reads a message. If nonblock is true, and there is no

+ 30 - 1
src/lib/python/isc/cc/tests/session_test.py

@@ -29,6 +29,7 @@ class MySocket():
         self.recvqueue = bytearray()
         self.recvqueue = bytearray()
         self.sendqueue = bytearray()
         self.sendqueue = bytearray()
         self._blocking = True
         self._blocking = True
+        self.send_limit = None
 
 
     def connect(self, to):
     def connect(self, to):
         pass
         pass
@@ -40,7 +41,14 @@ class MySocket():
         self._blocking = val
         self._blocking = val
 
 
     def send(self, data):
     def send(self, data):
-        self.sendqueue.extend(data);
+        # If the upper limit is specified, only "send" up to the specified
+        # limit
+        if self.send_limit is not None and len(data) > self.send_limit:
+            self.sendqueue.extend(data[0:self.send_limit])
+            return self.send_limit
+        else:
+            self.sendqueue.extend(data)
+            return len(data)
 
 
     def readsent(self, length):
     def readsent(self, length):
         if length > len(self.sendqueue):
         if length > len(self.sendqueue):
@@ -101,6 +109,17 @@ class MySocket():
     def gettimeout(self):
     def gettimeout(self):
         return 0
         return 0
 
 
+    def set_send_limit(self, limit):
+        '''Specify the upper limit of the transmittable data at once.
+
+        By default, the send() method of this class "sends" all given data.
+        If this method is called and the its parameter is not None,
+        subsequent calls to send() will only transmit the specified amount
+        of data.  This can be used to emulate the situation where send()
+        on a real socket object results in partial write.
+        '''
+        self.send_limit = limit
+
 #
 #
 # We subclass the Session class we're testing here, only
 # We subclass the Session class we're testing here, only
 # to override the __init__() method, which wants a socket,
 # to override the __init__() method, which wants a socket,
@@ -157,6 +176,16 @@ class testSession(unittest.TestCase):
         #print(sent)
         #print(sent)
         #self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
         #self.assertRaises(SessionError, sess.sendmsg, {}, {"hello": "a"})
 
 
+    def test_session_sendmsg_shortwrite(self):
+        sess = MySession()
+        # Specify the upper limit of the size that can be transmitted at
+        # a single send() call on the faked socket (10 is an arbitrary choice,
+        # just reasonably small).
+        sess._socket.set_send_limit(10)
+        sess.sendmsg({'to': 'someone', 'reply': 1}, {"hello": "a"})
+        # The complete message should still have been transmitted in the end.
+        sent = sess._socket.readsentmsg();
+
     def recv_and_compare(self, session, bytes, env, msg):
     def recv_and_compare(self, session, bytes, env, msg):
         """Adds bytes to the recvqueue (which will be read by the
         """Adds bytes to the recvqueue (which will be read by the
            session object, and compare the resultinv env and msg to
            session object, and compare the resultinv env and msg to

+ 11 - 8
src/lib/python/isc/config/config_data.py

@@ -109,14 +109,17 @@ def convert_type(spec_part, value):
 
 
             return ret
             return ret
         elif data_type == "map":
         elif data_type == "map":
-            map = ast.literal_eval(value)
-            if type(map) == dict:
-                # todo: check types of map contents too
-                return map
-            else:
-                raise isc.cc.data.DataTypeError(
-                           "Value in convert_type not a string "
-                           "specifying a dict")
+            try:
+                map = ast.literal_eval(value)
+                if type(map) == dict:
+                    # todo: check types of map contents too
+                    return map
+                else:
+                    raise isc.cc.data.DataTypeError(
+                               "Value in convert_type not a string "
+                               "specifying a dict")
+            except SyntaxError as se:
+                raise isc.cc.data.DataTypeError("Error parsing map: " + str(se))
         else:
         else:
             return value
             return value
     except ValueError as err:
     except ValueError as err:

+ 1 - 0
src/lib/python/isc/config/tests/config_data_test.py

@@ -157,6 +157,7 @@ class TestConfigData(unittest.TestCase):
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "a", "b" ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "a", "b" ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "\"{ \"a\": 1 }\"")
 
 
         spec_part = find_spec_part(config_spec, "value7")
         spec_part = find_spec_part(config_spec, "value7")
         self.assertEqual(['1', '2'], convert_type(spec_part, '1, 2'))
         self.assertEqual(['1', '2'], convert_type(spec_part, '1, 2'))

+ 1 - 1
src/lib/python/isc/datasrc/Makefile.am

@@ -22,7 +22,7 @@ datasrc_la_SOURCES += journal_reader_python.cc journal_reader_python.h
 datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 datasrc_la_LDFLAGS = $(PYTHON_LDFLAGS)
 datasrc_la_LDFLAGS = $(PYTHON_LDFLAGS)
-datasrc_la_LDFLAGS += -module
+datasrc_la_LDFLAGS += -module -avoid-version
 datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
 datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la

+ 1 - 0
src/lib/python/isc/datasrc/tests/.gitignore

@@ -0,0 +1 @@
+/*.sqlite3.copied

+ 1 - 1
src/lib/python/isc/log/Makefile.am

@@ -13,7 +13,7 @@ log_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 # placed after -Wextra defined in AM_CXXFLAGS
 # placed after -Wextra defined in AM_CXXFLAGS
 log_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 log_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 log_la_LDFLAGS = $(PYTHON_LDFLAGS)
 log_la_LDFLAGS = $(PYTHON_LDFLAGS)
-log_la_LDFLAGS += -module
+log_la_LDFLAGS += -module -avoid-version
 log_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
 log_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
 log_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
 log_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
 log_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la
 log_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la

+ 1 - 1
src/lib/python/isc/util/cio/Makefile.am

@@ -23,7 +23,7 @@ socketsession_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # suffix for dynamic objects.  -module is necessary to work this around.
-socketsession_la_LDFLAGS += -module
+socketsession_la_LDFLAGS += -module -avoid-version
 socketsession_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la
 socketsession_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la
 socketsession_la_LIBADD += $(PYTHON_LIB)
 socketsession_la_LIBADD += $(PYTHON_LIB)
 
 

+ 1 - 1
src/lib/util/io/Makefile.am

@@ -13,7 +13,7 @@ CLEANFILES = *.gcno *.gcda
 pyexec_LTLIBRARIES = libutil_io_python.la
 pyexec_LTLIBRARIES = libutil_io_python.la
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # suffix for dynamic objects.  -module is necessary to work this around.
-libutil_io_python_la_LDFLAGS = -module
+libutil_io_python_la_LDFLAGS = -module -avoid-version
 libutil_io_python_la_SOURCES = fdshare_python.cc
 libutil_io_python_la_SOURCES = fdshare_python.cc
 libutil_io_python_la_LIBADD = libutil_io.la
 libutil_io_python_la_LIBADD = libutil_io.la
 libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)

+ 1 - 1
src/lib/util/pyunittests/Makefile.am

@@ -13,7 +13,7 @@ pyunittests_util_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
 
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # suffix for dynamic objects.  -module is necessary to work this around.
-pyunittests_util_la_LDFLAGS += -module
+pyunittests_util_la_LDFLAGS += -module -avoid-version
 pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
 pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
 pyunittests_util_la_LIBADD += $(PYTHON_LIB)
 pyunittests_util_la_LIBADD += $(PYTHON_LIB)
 
 

+ 34 - 38
tests/lettuce/features/nsec3_auth.feature

@@ -160,45 +160,41 @@ Feature: NSEC3 Authoritative service
     # Below are additional tests, not explicitely stated in RFC5155
     # Below are additional tests, not explicitely stated in RFC5155
     #
     #
 
 
-    # THIS TEST CURRENTLY FAILS: An NSEC3 record is added twice
-    # See ticket #1688
-    #Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (closest encloser)
-    #    Given I have bind10 running with configuration nsec3/nsec3_auth.config
-    #    A dnssec query for b.x.w.example. should have rcode NXDOMAIN
-    #    The last query response should have flags qr aa rd
-    #    The last query response should have edns_flags do
-    #    The last query response should have ancount 0
-    #    The last query response should have nscount 6
-    #    The last query response should have adcount 1
-    #    The authority section of the last query response should be
-    #    """
-    #    example.	3600	IN	SOA	ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
-    #    example.	3600	IN	RRSIG	SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
-    #    b4um86eghhds6nea196smvmlo4ors995.example.	3600	IN	NSEC3	1 1 12 aabbccdd  gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG 
-    #    b4um86eghhds6nea196smvmlo4ors995.example.	3600	IN	RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
-    #    35mthgpgcu1qg68fab165klnsnk3dpvl.example.	3600	IN	NSEC3	1 1 12 aabbccdd  b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG 
-    #    35mthgpgcu1qg68fab165klnsnk3dpvl.example.	3600	IN	RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
-    #    """
+    Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (closest encloser)
+        Given I have bind10 running with configuration nsec3/nsec3_auth.config
+        A dnssec query for b.x.w.example. should have rcode NXDOMAIN
+        The last query response should have flags qr aa rd
+        The last query response should have edns_flags do
+        The last query response should have ancount 0
+        The last query response should have nscount 6
+        The last query response should have adcount 1
+        The authority section of the last query response should be
+        """
+        example.	3600	IN	SOA	ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+        example.	3600	IN	RRSIG	SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+        b4um86eghhds6nea196smvmlo4ors995.example.	3600	IN	NSEC3	1 1 12 aabbccdd  gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG 
+        b4um86eghhds6nea196smvmlo4ors995.example.	3600	IN	RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+        35mthgpgcu1qg68fab165klnsnk3dpvl.example.	3600	IN	NSEC3	1 1 12 aabbccdd  b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG 
+        35mthgpgcu1qg68fab165klnsnk3dpvl.example.	3600	IN	RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+        """
 
 
-    # THIS TEST CURRENTLY FAILS: An NSEC3 record is added twice
-    # See ticket #1688
-    #Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (wildcard)
-    #    Given I have bind10 running with configuration nsec3/nsec3_auth.config
-    #    A dnssec query for a.w.example. should have rcode NXDOMAIN
-    #    The last query response should have flags qr aa rd
-    #    The last query response should have edns_flags do
-    #    The last query response should have ancount 0
-    #    The last query response should have nscount 6
-    #    The last query response should have adcount 1
-    #    The authority section of the last query response should be
-    #    """
-    #    example.		3600	IN	SOA	ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
-    #    example.		3600	IN	RRSIG	SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
-    #    k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3	1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
-    #    k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
-    #    r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3	1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
-    #    r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
-    #    """
+    Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (wildcard)
+        Given I have bind10 running with configuration nsec3/nsec3_auth.config
+        A dnssec query for a.w.example. should have rcode NOERROR
+        The last query response should have flags qr aa rd
+        The last query response should have edns_flags do
+        The last query response should have ancount 0
+        The last query response should have nscount 6
+        The last query response should have adcount 1
+        The authority section of the last query response should be
+        """
+        example.		3600	IN	SOA	ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+        example.		3600	IN	RRSIG	SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+        k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3	1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+        k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+        r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3	1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+        r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+        """
 
 
     Scenario: Wildcard other: Wildcard name itself
     Scenario: Wildcard other: Wildcard name itself
         Given I have bind10 running with configuration nsec3/nsec3_auth.config
         Given I have bind10 running with configuration nsec3/nsec3_auth.config