Browse Source

[1307] Merge branch 'trac1305' into trac1307

JINMEI Tatuya 13 years ago
parent
commit
84ded89c23

+ 9 - 5
src/lib/datasrc/database.cc

@@ -417,7 +417,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
     size_t last_known(origin_label_count);
     const size_t current_label_count(name.getLabelCount());
     // This is how many labels we remove to get origin
-    size_t remove_labels(current_label_count - origin_label_count);
+    const size_t remove_labels(current_label_count - origin_label_count);
 
     // Now go trough all superdomains from origin down
     for (int i(remove_labels); i > 0; --i) {
@@ -508,13 +508,17 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                     arg(accessor_->getDBName()).arg(name);
                 records_found = true;
                 get_cover = dnssec_data;
+            } else if ((options & NO_WILDCARD) != 0) {
+                // If wildcard check is disabled, terminate the search with
+                // NXDOMAIN.
+                if (dnssec_data && !records_found) {
+                    get_cover = true;
+                }
             } else {
                 // It's not empty non-terminal. So check for wildcards.
                 // We remove labels one by one and look for the wildcard there.
                 // Go up to first non-empty domain.
-
-                remove_labels = current_label_count - last_known;
-                for (size_t i(1); i <= remove_labels; ++ i) {
+                for (size_t i(1); i <= current_label_count - last_known; ++i) {
                     // Construct the name with *
                     const Name superdomain(name.split(i));
                     const string wildcard("*." + superdomain.toText());
@@ -553,7 +557,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                             if (cni != found.second.end() &&
                                 type != RRType::CNAME()) {
                                 result_rrset = cni->second;
-                                result_status = CNAME;
+                                result_status = WILDCARD_CNAME;
                             } else if (nsi != found.second.end()) {
                                 result_rrset = nsi->second;
                                 result_status = DELEGATION;

+ 113 - 24
src/lib/datasrc/tests/database_unittest.cc

@@ -175,8 +175,11 @@ const char* const TEST_RECORDS[][5] = {
     {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
     {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
     {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
+    {"wild.*.foo.*.bar.example.org.", "NSEC", "3600", "",
+     "brokenns1.example.org. A NSEC"},
     {"bao.example.org.", "NSEC", "3600", "", "wild.*.foo.*.bar.example.org. NSEC"},
     {"*.cnamewild.example.org.", "CNAME", "3600", "", "www.example.org."},
+    {"*.dnamewild.example.org.", "DNAME", "3600", "", "dname.example.com."},
     {"*.nswild.example.org.", "NS", "3600", "", "ns.example.com."},
     // For NSEC empty non-terminal
     {"l.example.org.", "NSEC", "3600", "", "empty.nonterminal.example.org. NSEC"},
@@ -258,9 +261,16 @@ private:
  * implementation of the optional functionality.
  */
 class MockAccessor : public NopAccessor {
-    // Type of mock database "row"s
-    typedef std::map<std::string, std::vector< std::vector<std::string> > >
-        Domains;
+    // Type of mock database "row"s.  This is a map whose keys are the
+    // own names.  We internally sort them by the name comparison order.
+    struct NameCompare : public binary_function<string, string, bool> {
+        bool operator()(const string& n1, const string n2) const {
+            return (Name(n1).compare(Name(n2)).getOrder() < 0);
+        }
+    };
+    typedef std::map<std::string,
+                     std::vector< std::vector<std::string> >,
+                     NameCompare > Domains;
 
 public:
     MockAccessor() : rollbacked_(false) {
@@ -554,30 +564,36 @@ public:
     virtual std::string findPreviousName(int id, const std::string& rname)
         const
     {
-        // Hardcoded for now, but we could compute it from the data
-        // Maybe do it when it is needed some time in future?
         if (id == -1) {
             isc_throw(isc::NotImplemented, "Test not implemented behaviour");
-        } else if (id == 42) {
-            if (rname == "org.example.nonterminal.") {
-                return ("l.example.org.");
-            } else if (rname == "org.example.aa.") {
-                return ("example.org.");
-            } else if (rname == "org.example.www2." ||
-                       rname == "org.example.www1.") {
-                return ("www.example.org.");
-            } else if (rname == "org.example.badnsec2.") {
+        } else if (id == READONLY_ZONE_ID) {
+            // For some specific names we intentionally return broken or
+            // unexpected result.
+            if (rname == "org.example.badnsec2.") {
                 return ("badnsec1.example.org.");
             } else if (rname == "org.example.brokenname.") {
                 return ("brokenname...example.org.");
-            } else if (rname == "org.example.bar.*.") {
-                return ("bao.example.org.");
             } else if (rname == "org.example.notimplnsec." ||
                        rname == "org.example.wild.here.") {
                 isc_throw(isc::NotImplemented, "Not implemented in this test");
-            } else {
+            }
+
+            // For the general case, we search for the first name N in the
+            // domains that meets N >= reverse(rname) using lower_bound.
+            // The "previous name" is the name of the previous entry of N.
+            // Note that Domains are internally sorted by the Name comparison
+            // order.  Due to the API requirement we are given a reversed
+            // name (rname), so we need to reverse it again to convert it
+            // to the original name.
+            Domains::const_iterator it(readonly_records_->lower_bound(
+                                           Name(rname).reverse().toText()));
+            if (it == readonly_records_->begin()) {
                 isc_throw(isc::Unexpected, "Unexpected name");
             }
+            if (it == readonly_records_->end()) {
+                return ((*readonly_records_->rbegin()).first);
+            }
+            return ((*(--it)).first);
         } else {
             isc_throw(isc::Unexpected, "Unknown zone ID");
         }
@@ -1027,8 +1043,8 @@ doFindTest(ZoneFinder& finder,
            const ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT)
 {
     SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
-    ZoneFinder::FindResult result =
-        finder.find(name, type, NULL, options);
+    const ZoneFinder::FindResult result = finder.find(name, type, NULL,
+                                                      options);
     ASSERT_EQ(expected_result, result.code) << name << " " << type;
     if (!expected_rdatas.empty() && result.rrset) {
         checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
@@ -1595,21 +1611,21 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
         "bar.example.org",
         NULL
     };
+    // Unless FIND_DNSSEC is specified, this is no different from other
+    // NXRRSET case.
     for (const char** name(negative_names); *name != NULL; ++ name) {
         doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
                    this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
                    this->expected_rdatas_, this->expected_sig_rdatas_);
-        // FIXME: What should be returned in this case? How does the
-        // DNSSEC logic handle it?
     }
 
+    // With FIND_DNSSEC, it should result in WILDCARD_EMPTY.
     const char* negative_dnssec_names[] = {
         "a.bar.example.org.",
         "foo.baz.bar.example.org.",
         "a.foo.bar.example.org.",
         NULL
     };
-
     this->expected_rdatas_.clear();
     this->expected_rdatas_.push_back("wild.*.foo.*.bar.example.org. NSEC");
     this->expected_sig_rdatas_.clear();
@@ -1620,15 +1636,27 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
                    Name("bao.example.org."), ZoneFinder::FIND_DNSSEC);
     }
 
-    // Some strange things in the wild node
+    // CNAME on a wildcard.  Maybe not so common, but not disallowed.
     this->expected_rdatas_.clear();
     this->expected_rdatas_.push_back("www.example.org.");
     this->expected_sig_rdatas_.clear();
     doFindTest(*finder, isc::dns::Name("a.cnamewild.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::CNAME(),
-               this->rrttl_, ZoneFinder::CNAME,
+               this->rrttl_, ZoneFinder::WILDCARD_CNAME,
                this->expected_rdatas_, this->expected_sig_rdatas_);
 
+    // DNAME on a wildcard.  In our implementation we ignore DNAMEs on a
+    // wildcard, but at a higher level we say the behavior is "unspecified".
+    // rfc2672bis strongly discourages the mixture of DNAME and wildcard
+    // (with SHOULD NOT).
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, Name("a.dnamewild.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_,
+               ZoneFinder::WILDCARD_NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    // Some strange things in the wild node
     this->expected_rdatas_.clear();
     this->expected_rdatas_.push_back("ns.example.com.");
     doFindTest(*finder, isc::dns::Name("a.nswild.example.org."),
@@ -1637,6 +1665,67 @@ TYPED_TEST(DatabaseClientTest, wildcard) {
                this->expected_rdatas_, this->expected_sig_rdatas_);
 }
 
+TYPED_TEST(DatabaseClientTest, noWildcard) {
+    // Tests with the NO_WILDCARD flag.
+
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    // This would match *.wild.example.org, but with NO_WILDCARD should
+    // result in NXDOMAIN.
+    this->expected_rdatas_.push_back("cancel.here.wild.example.org. A "
+                                     "NSEC RRSIG");
+    this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
+               RRType::NSEC(), RRType::NSEC(), this->rrttl_,
+               ZoneFinder::NXDOMAIN, this->expected_rdatas_,
+               this->expected_sig_rdatas_, Name("*.wild.example.org."),
+               ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
+
+    // Should be the same without FIND_DNSSEC (but in this case no RRsets
+    // will be returned)
+    doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
+               RRType::NSEC(), RRType::NSEC(), this->rrttl_,
+               ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+               this->empty_rdatas_, Name::ROOT_NAME(), // name is dummy
+               ZoneFinder::NO_WILDCARD);
+
+    // Same for wildcard empty non terminal.
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("brokenns1.example.org. A NSEC");
+    doFindTest(*finder, isc::dns::Name("a.bar.example.org"),
+               RRType::NSEC(), RRType::NSEC(), this->rrttl_,
+               ZoneFinder::NXDOMAIN, this->expected_rdatas_,
+               this->empty_rdatas_, Name("wild.*.foo.*.bar.example.org"),
+               ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
+
+    // Search for a wildcard name with NO_WILDCARD.  There should be no
+    // difference.  This is, for example, necessary to provide non existence
+    // of matching wildcard for isnx.nonterminal.example.org.
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("empty.nonterminal.example.org. NSEC");
+    doFindTest(*finder, isc::dns::Name("*.nonterminal.example.org"),
+               RRType::NSEC(), RRType::NSEC(), this->rrttl_,
+               ZoneFinder::NXDOMAIN, this->expected_rdatas_,
+               this->empty_rdatas_, Name("l.example.org"),
+               ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
+
+    // On the other hand, if there's exact match for the wildcard name
+    // it should be found regardless of NO_WILDCARD.
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.5");
+    this->expected_sig_rdatas_.clear();
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_,
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_, Name("*.wild.example.org"),
+               ZoneFinder::NO_WILDCARD);
+}
+
 TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
     // The domain exists, but doesn't have this RRType
     // So we should get its NSEC

+ 70 - 23
src/lib/datasrc/zone.h

@@ -63,32 +63,70 @@ public:
     /// actually the best wildcard we have). Data sources that don't
     /// support DNSSEC don't need to distinguish them.
     ///
-    /// In case of NXRRSET related results, the returned NSEC record
-    /// belongs to the domain which would provide the result if it
-    /// contained the correct type (in case of NXRRSET, it is the queried
-    /// domain, in case of WILDCARD_NXRRSET, it is the wildcard domain
-    /// that matched the query name). In case of an empty nonterminal,
-    /// an NSEC is provided for the interval where the empty nonterminal
-    /// lives. The end of the interval is the subdomain causing existence
-    /// of the empty nonterminal (if there's sub.x.example.com, and no record
-    /// in x.example.com, then x.example.com exists implicitly - is the empty
-    /// nonterminal and sub.x.example.com is the subdomain causing it).
+    /// In case of CNAME, if the CNAME is a wildcard (i.e., its owner name
+    /// starts with the label "*"), WILDCARD_CNAME will be returned instead
+    /// of CNAME.
+    ///
+    /// In case of NXDOMAIN, the returned NSEC covers the queried domain
+    /// that proves that the query name does not exist in the zone.  Note that
+    /// this does not necessarily prove it doesn't even match a wildcard
+    /// (even if the result of NXDOMAIN can only happen when there's no
+    /// matching wildcard either).  It is caller's responsibility to provide
+    /// a proof that there is no matching wildcard if that proof is necessary.
+    ///
+    /// Various variants of "no data" cases are complicated, when involves
+    /// DNSSEC and wildcard processing.  Referring to Section 3.1.3 of
+    /// RFC4035, we need to consider the following cases:
+    /// -# (Normal) no data: there is a matching non-wildcard name with a
+    ///    different RR type.  This is the "No Data" case of the RFC.
+    /// -# (Normal) empty non terminal: there is no matching (exact or
+    ///    wildcard) name, but there is a subdomain with an RR of the query
+    ///    name.  This is one case of "Name Error" of the RFC.
+    /// -# Wildcard empty non terminal: similar to 2a, but the empty name
+    ///    is a wildcard, and matches the query name by wildcard expansion.
+    ///    This is a special case of "Name Error" of the RFC.
+    /// -# Wildcard no data: there is no exact match name, but there is a
+    ///    wildcard name that matches the query name with a different type
+    ///    of RR.  This is the "Wildcard No Data" case of the RFC.
+    ///
+    /// In any case, \c find() will result in \c NXRRSET with no RRset
+    /// unless the \c FIND_DNSSEC option is specified.  The rest of the
+    /// discussion only applies to the case where this option is specified.
+    ///
+    /// In case 1, \c find() will result in NXRRSET, and return NSEC of the
+    /// matching name.
+    ///
+    /// In case 2, \c find() will result in NXRRSET, and return NSEC for the
+    /// interval where the empty nonterminal lives. The end of the interval
+    /// is the subdomain causing existence of the empty nonterminal (if
+    /// there's sub.x.example.com, and no record in x.example.com, then
+    /// x.example.com exists implicitly - is the empty nonterminal and
+    /// sub.x.example.com is the subdomain causing it).  Note that this NSEC
+    /// proves not only the existence of empty non terminal name but also
+    /// the non existence of possibly matching wildcard name, because
+    /// there can be no better wildcard match than the exact matching empty
+    /// name.
+    ///
+    /// In case 3, \c find() will result in WILDCARD_NXRRSET, and return NSEC
+    /// for the interval where the wildcard empty nonterminal lives.
+    /// Cases 2 and 3 are especially complicated and confusing.  See the
+    /// examples below.
+    ///
+    /// In case 4, \c find() will result in WILDCARD_NXRRSET, and return
+    /// NSEC of the matching wildcard name.
     ///
     /// Examples: if zone "example.com" has the following record:
     /// \code
-    /// a.b.example.com. NSEC c.example.com.
+    /// a.example.com. NSEC a.b.example.com.
     /// \endcode
-    /// a call to \c find() for "b.example.com." will result in NXRRSET,
-    /// and if the FIND_DNSSEC option is set this NSEC will be returned.
+    /// a call to \c find() for "b.example.com." with the FIND_DNSSEC option
+    /// will result in NXRRSET, and this NSEC will be returned.
     /// Likewise, if zone "example.org" has the following record,
     /// \code
-    /// x.*.example.org. NSEC a.example.org.
+    /// a.example.org. NSEC x.*.b.example.org.
     /// \endcode
-    /// a call to \c find() for "y.example.org" will result in
-    /// WILDCARD_NXRRSET (*.example.org is an empty nonterminal wildcard node),
-    /// and if the FIND_DNSSEC option is set this NSEC will be returned.
-    ///
-    /// In case of NXDOMAIN, the returned NSEC covers the queried domain.
+    /// a call to \c find() for "y.b.example.org" with FIND_DNSSEC will
+    /// result in NXRRSET_NXRRSET, and this NSEC will be returned.
     enum Result {
         SUCCESS,                ///< An exact match is found.
         DELEGATION,             ///< The search encounters a zone cut.
@@ -97,6 +135,7 @@ public:
         CNAME,    ///< The search encounters and returns a CNAME RR
         DNAME,    ///< The search encounters and returns a DNAME RR
         WILDCARD, ///< Succes by wildcard match, for DNSSEC
+        WILDCARD_CNAME, ///< CNAME on wildcard, search returns CNAME, for DNSSEC
         WILDCARD_NXRRSET ///< NXRRSET on wildcard, for DNSSEC
     };
 
@@ -138,10 +177,11 @@ public:
     enum FindOptions {
         FIND_DEFAULT = 0,       ///< The default options
         FIND_GLUE_OK = 1,       ///< Allow search under a zone cut
-        FIND_DNSSEC = 2         ///< Require DNSSEC data in the answer
+        FIND_DNSSEC = 2,        ///< Require DNSSEC data in the answer
                                 ///< (RRSIG, NSEC, etc.). The implementation
                                 ///< is allowed to include it even if it is
                                 ///< not set.
+        NO_WILDCARD = 4         ///< Do not try wildcard matching.
     };
 
     ///
@@ -181,6 +221,7 @@ public:
     /// for the data that best matches the given name and type.
     /// This method is expected to be "intelligent", and identifies the
     /// best possible answer for the search key.  Specifically,
+    ///
     /// - If the search name belongs under a zone cut, it returns the code
     ///   of \c DELEGATION and the NS RRset at the zone cut.
     /// - If there is no matching name, it returns the code of \c NXDOMAIN,
@@ -199,12 +240,14 @@ public:
     /// - If the target isn't NULL, all RRsets under the domain are inserted
     ///   there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned
     ///   instead of normall processing. This is intended to handle ANY query.
-    ///   \note: this behavior is controversial as we discussed in
-    ///   https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html
-    ///   We should revisit the interface before we heavily rely on it.
+    ///
+    /// \note This behavior is controversial as we discussed in
+    /// https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html
+    /// We should revisit the interface before we heavily rely on it.
     ///
     /// The \c options parameter specifies customized behavior of the search.
     /// Their semantics is as follows (they are or bit-field):
+    ///
     /// - \c FIND_GLUE_OK Allow search under a zone cut.  By default the search
     ///   will stop once it encounters a zone cut.  If this option is specified
     ///   it remembers information about the highest zone cut and continues
@@ -216,6 +259,10 @@ public:
     /// - \c FIND_DNSSEC Request that DNSSEC data (like NSEC, RRSIGs) are
     ///   returned with the answer. It is allowed for the data source to
     ///   include them even when not requested.
+    /// - \c NO_WILDCARD Do not try wildcard matching.  This option is of no
+    ///   use for normal lookups; it's intended to be used to get a DNSSEC
+    ///   proof of the non existence of any matching wildcard or non existence
+    ///   of an exact match when a wildcard match is found.
     ///
     /// A derived version of this method may involve internal resource
     /// allocation, especially for constructing the resulting RRset, and may

+ 4 - 0
src/lib/python/isc/datasrc/datasrc.cc

@@ -132,6 +132,8 @@ initModulePart_ZoneFinder(PyObject* mod) {
                              Py_BuildValue("I", ZoneFinder::WILDCARD));
         installClassVariable(zonefinder_type, "WILDCARD_NXRRSET",
                              Py_BuildValue("I", ZoneFinder::WILDCARD_NXRRSET));
+        installClassVariable(zonefinder_type, "WILDCARD_CNAME",
+                             Py_BuildValue("I", ZoneFinder::WILDCARD_CNAME));
 
         installClassVariable(zonefinder_type, "FIND_DEFAULT",
                              Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
@@ -139,6 +141,8 @@ initModulePart_ZoneFinder(PyObject* mod) {
                              Py_BuildValue("I", ZoneFinder::FIND_GLUE_OK));
         installClassVariable(zonefinder_type, "FIND_DNSSEC",
                              Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
+        installClassVariable(zonefinder_type, "NO_WILDCARD",
+                             Py_BuildValue("I", ZoneFinder::NO_WILDCARD));
     } catch (const std::exception& ex) {
         const std::string ex_what =
             "Unexpected failure in ZoneFinder initialization: " +

+ 36 - 21
src/lib/python/isc/datasrc/finder_inc.cc

@@ -42,11 +42,20 @@ Return the RR class of the zone.\n\
 \n\
 ";
 
+// Main changes from the C++ doxygen version:
+// - Return type: use tuple instead of the dedicated FindResult type
+// - NULL->None
+// - exceptions
 const char* const ZoneFinder_find_doc = "\
-find(name, type, target=NULL, options=FIND_DEFAULT) -> (code, FindResult)\n\
+find(name, type, target=None, options=FIND_DEFAULT) -> (integer, RRset)\n\
 \n\
 Search the zone for a given pair of domain name and RR type.\n\
 \n\
+Each derived version of this method searches the underlying backend\n\
+for the data that best matches the given name and type. This method is\n\
+expected to be \"intelligent\", and identifies the best possible\n\
+answer for the search key. Specifically,\n\
+\n\
 - If the search name belongs under a zone cut, it returns the code of\n\
   DELEGATION and the NS RRset at the zone cut.\n\
 - If there is no matching name, it returns the code of NXDOMAIN, and,\n\
@@ -62,40 +71,46 @@ Search the zone for a given pair of domain name and RR type.\n\
   and the code of SUCCESS will be returned.\n\
 - If the search name matches a delegation point of DNAME, it returns\n\
   the code of DNAME and that DNAME RR.\n\
-- If the result was synthesized by a wildcard match, it returns the\n\
-  code WILDCARD and the synthesized RRset\n\
-- If the query matched a wildcard name, but not its type, it returns the\n\
-  code WILDCARD_NXRRSET, and None\n\
-- If the target is a list, all RRsets under the domain are inserted\n\
+- If the target isn't None, all RRsets under the domain are inserted\n\
   there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned\n\
   instead of normall processing. This is intended to handle ANY query.\n\
-  : this behavior is controversial as we discussed in\n\
-  https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html\n\
-  We should revisit the interface before we heavily rely on it. The\n\
-  options parameter specifies customized behavior of the search. Their\n\
-  semantics is as follows:\n\
-  (This feature is disable at this time)\n\
-- GLUE_OK Allow search under a zone cut. By default the search will\n\
-  stop once it encounters a zone cut. If this option is specified it\n\
-  remembers information about the highest zone cut and continues the\n\
-  search until it finds an exact match for the given name or it\n\
+\n\
+Note: This behavior is controversial as we discussed in\n\
+https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html We\n\
+should revisit the interface before we heavily rely on it.\n\
+\n\
+The options parameter specifies customized behavior of the search.\n\
+Their semantics is as follows (they are or bit-field):\n\
+\n\
+- FIND_GLUE_OK Allow search under a zone cut. By default the search\n\
+  will stop once it encounters a zone cut. If this option is specified\n\
+  it remembers information about the highest zone cut and continues\n\
+  the search until it finds an exact match for the given name or it\n\
   detects there is no exact match. If an exact match is found, RRsets\n\
   for that name are searched just like the normal case; otherwise, if\n\
   the search has encountered a zone cut, DELEGATION with the\n\
   information of the highest zone cut will be returned.\n\
+- FIND_DNSSEC Request that DNSSEC data (like NSEC, RRSIGs) are\n\
+  returned with the answer. It is allowed for the data source to\n\
+  include them even when not requested.\n\
+- NO_WILDCARD Do not try wildcard matching. This option is of no use\n\
+  for normal lookups; it's intended to be used to get a DNSSEC proof\n\
+  of the non existence of any matching wildcard or non existence of an\n\
+  exact match when a wildcard match is found.\n\
+\n\
 \n\
-This method raises an isc.datasrc.Error exception if there is an internal\n\
-error in the datasource.\n\
+This method raises an isc.datasrc.Error exception if there is an\n\
+internal error in the datasource.\n\
 \n\
 Parameters:\n\
   name       The domain name to be searched for.\n\
   type       The RR type to be searched for.\n\
-  target     If target is not NULL, insert all RRs under the domain\n\
+  target     If target is not None, insert all RRs under the domain\n\
              into it.\n\
   options    The search options.\n\
 \n\
-Return Value(s): A tuple of a result code an a FindResult object enclosing\n\
-the search result (see above).\n\
+Return Value(s): A tuple of a result code (integer) and an RRset object\n\
+enclosing the search result (see above).\n\
 ";
 
 const char* const ZoneFinder_find_previous_name_doc = "\

+ 24 - 0
src/lib/python/isc/datasrc/tests/datasrc_test.py

@@ -15,6 +15,7 @@
 
 import isc.log
 import isc.datasrc
+from isc.datasrc import ZoneFinder
 import isc.dns
 import unittest
 import os
@@ -191,6 +192,29 @@ class DataSrcClient(unittest.TestCase):
         # can't construct directly
         self.assertRaises(TypeError, isc.datasrc.ZoneFinder)
 
+    def test_findoptions(self):
+        '''A simple test to confirm no option is specified by default.
+
+        '''
+        self.assertFalse(ZoneFinder.FIND_DEFAULT & ZoneFinder.FIND_GLUE_OK)
+        self.assertFalse(ZoneFinder.FIND_DEFAULT & ZoneFinder.FIND_DNSSEC)
+        self.assertFalse(ZoneFinder.FIND_DEFAULT & ZoneFinder.NO_WILDCARD)
+
+    def test_findresults(self):
+        '''A simple test to confirm result codes are (defined and) different
+        for some combinations.
+
+        '''
+        self.assertNotEqual(ZoneFinder.SUCCESS, ZoneFinder.DELEGATION)
+        self.assertNotEqual(ZoneFinder.DELEGATION, ZoneFinder.NXDOMAIN)
+        self.assertNotEqual(ZoneFinder.NXDOMAIN, ZoneFinder.NXRRSET)
+        self.assertNotEqual(ZoneFinder.NXRRSET, ZoneFinder.CNAME)
+        self.assertNotEqual(ZoneFinder.CNAME, ZoneFinder.DNAME)
+        self.assertNotEqual(ZoneFinder.DNAME, ZoneFinder.WILDCARD)
+        self.assertNotEqual(ZoneFinder.WILDCARD, ZoneFinder.WILDCARD_CNAME)
+        self.assertNotEqual(ZoneFinder.WILDCARD_CNAME,
+                            ZoneFinder.WILDCARD_NXRRSET)
+
     def test_find(self):
         dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)