Browse Source

[master]Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10

Jeremy C. Reed 13 years ago
parent
commit
c6385a4f54

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+451.	[bug]		muks, jinmei
+	libdatasrc: the database-based data source now correctly returns
+	glue records on (not under) a zone cut, such as in the case where
+	the NS name of an NS record is identical to its owner name. (Note:
+	libdatasrc itself doesn't judge what kind of record type can be a
+	"glue"; it's the caller's responsibility.)
+	(Trac #1771, git 483f1075942965f0340291e7ff7dae7806df22af)
+
 450.	[func]*		tomek
 450.	[func]*		tomek
 	b10-dhcp4: DHCPv4 server component is now integrated into
 	b10-dhcp4: DHCPv4 server component is now integrated into
 	BIND10 framework. It can be started from BIND10 (using bindctl)
 	BIND10 framework. It can be started from BIND10 (using bindctl)

+ 3 - 0
src/bin/auth/auth_messages.mes

@@ -110,6 +110,9 @@ look into the cause and address the issue.  The log message includes
 the client's address (and port), and the error message sent from the
 the client's address (and port), and the error message sent from the
 lower layer that detects the failure.
 lower layer that detects the failure.
 
 
+% AUTH_RECEIVED_NOTIFY received incoming NOTIFY for zone name %1, zone class %2
+This is a debug message reporting that an incoming NOTIFY was received.
+
 % AUTH_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
 % AUTH_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
 This debug message is logged by the authoritative server when it receives
 This debug message is logged by the authoritative server when it receives
 a NOTIFY packet that contains zero or more than one question. (A valid
 a NOTIFY packet that contains zero or more than one question. (A valid

+ 3 - 0
src/bin/auth/auth_srv.cc

@@ -828,6 +828,9 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
         return (false);
         return (false);
     }
     }
 
 
+    LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RECEIVED_NOTIFY)
+      .arg(question->getName()).arg(question->getClass());
+
     const string remote_ip_address =
     const string remote_ip_address =
         io_message.getRemoteEndpoint().getAddress().toText();
         io_message.getRemoteEndpoint().getAddress().toText();
     static const string command_template_start =
     static const string command_template_start =

+ 4 - 0
src/bin/xfrin/xfrin.py.in

@@ -889,6 +889,10 @@ class XfrinConnection(asyncore.dispatcher):
             req_str = 'IXFR' if request_type == RRType.IXFR() else 'AXFR'
             req_str = 'IXFR' if request_type == RRType.IXFR() else 'AXFR'
             if check_soa:
             if check_soa:
                 self._check_soa_serial()
                 self._check_soa_serial()
+                self.close()
+                self.init_socket()
+                if not self.connect_to_master():
+                    raise XfrinException('Unable to reconnect to master')
 
 
             logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
             logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
             self._send_query(self._request_type)
             self._send_query(self._request_type)

+ 3 - 0
src/bin/xfrout/xfrout.py.in

@@ -952,6 +952,9 @@ class XfroutServer:
     def _start_notifier(self):
     def _start_notifier(self):
         datasrc = self._unix_socket_server.get_db_file()
         datasrc = self._unix_socket_server.get_db_file()
         self._notifier = notify_out.NotifyOut(datasrc)
         self._notifier = notify_out.NotifyOut(datasrc)
+        if 'also_notify' in self._config_data:
+            for slave in self._config_data['also_notify']:
+                self._notifier.add_slave(slave['address'], slave['port'])
         self._notifier.dispatcher()
         self._notifier.dispatcher()
 
 
     def send_notify(self, zone_name, zone_class):
     def send_notify(self, zone_name, zone_class):

+ 27 - 0
src/bin/xfrout/xfrout.spec.pre.in

@@ -21,6 +21,33 @@
          }
          }
        },
        },
        {
        {
+         "item_name": "also_notify",
+         "item_type": "list",
+         "item_optional": true,
+         "item_default": [],
+         "list_item_spec":
+         {
+             "item_name": "also_notify_element",
+             "item_type": "map",
+             "item_optional": true,
+             "item_default": {},
+             "map_item_spec": [
+               {
+                   "item_name": "address",
+                   "item_type": "string",
+                   "item_optional": false,
+                   "item_default": ""
+               },
+               {
+                   "item_name": "port",
+                   "item_type": "integer",
+                   "item_optional": false,
+                   "item_default": 0
+               }
+             ]
+         }
+       },
+       {
          "item_name": "zone_config",
          "item_name": "zone_config",
          "item_type": "list",
          "item_type": "list",
          "item_optional": true,
          "item_optional": true,

+ 22 - 35
src/lib/datasrc/database.cc

@@ -179,8 +179,7 @@ private:
 
 
 DatabaseClient::Finder::FoundRRsets
 DatabaseClient::Finder::FoundRRsets
 DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
 DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
-                                  bool check_ns, const string* construct_name,
+                                  const string* construct_name, bool any,
-                                  bool any,
                                   DatabaseAccessor::IteratorContextPtr context)
                                   DatabaseAccessor::IteratorContextPtr context)
 {
 {
     RRsigStore sig_store;
     RRsigStore sig_store;
@@ -204,9 +203,7 @@ DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
     const Name construct_name_object(*construct_name);
     const Name construct_name_object(*construct_name);
 
 
     bool seen_cname(false);
     bool seen_cname(false);
-    bool seen_ds(false);
     bool seen_other(false);
     bool seen_other(false);
-    bool seen_ns(false);
 
 
     while (context->getNext(columns)) {
     while (context->getNext(columns)) {
         // The domain is not empty
         // The domain is not empty
@@ -249,16 +246,12 @@ DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
 
 
             if (cur_type == RRType::CNAME()) {
             if (cur_type == RRType::CNAME()) {
                 seen_cname = true;
                 seen_cname = true;
-            } else if (cur_type == RRType::NS()) {
-                seen_ns = true;
-            } else if (cur_type == RRType::DS()) {
-                seen_ds = true;
             } else if (cur_type != RRType::RRSIG() &&
             } else if (cur_type != RRType::RRSIG() &&
                        cur_type != RRType::NSEC3() &&
                        cur_type != RRType::NSEC3() &&
                        cur_type != RRType::NSEC()) {
                        cur_type != RRType::NSEC()) {
                 // NSEC and RRSIG can coexist with anything, otherwise
                 // NSEC and RRSIG can coexist with anything, otherwise
                 // we've seen something that can't live together with potential
                 // we've seen something that can't live together with potential
-                // CNAME or NS
+                // CNAME.
                 //
                 //
                 // NSEC3 lives in separate namespace from everything, therefore
                 // NSEC3 lives in separate namespace from everything, therefore
                 // we just ignore it here for these checks as well.
                 // we just ignore it here for these checks as well.
@@ -278,14 +271,10 @@ DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
                       RDATA_COLUMN]);
                       RDATA_COLUMN]);
         }
         }
     }
     }
-    if (seen_cname && (seen_other || seen_ns || seen_ds)) {
+    if (seen_cname && seen_other) {
         isc_throw(DataSourceError, "CNAME shares domain " << name <<
         isc_throw(DataSourceError, "CNAME shares domain " << name <<
                   " with something else");
                   " with something else");
     }
     }
-    if (check_ns && seen_ns && seen_other) {
-        isc_throw(DataSourceError, "NS shares domain " << name <<
-                  " with something else");
-    }
     // Add signatures to all found RRsets
     // Add signatures to all found RRsets
     for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
     for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
          i != result.end(); ++ i) {
          i != result.end(); ++ i) {
@@ -455,20 +444,20 @@ DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
     for (int i = remove_labels; i > 0; --i) {
     for (int i = remove_labels; i > 0; --i) {
         const Name superdomain(name.split(i));
         const Name superdomain(name.split(i));
 
 
-        // Note if this is the origin. (We don't count NS records at the origin
-        // as a delegation so this controls whether NS RRs are included in
-        // the results of some searches.)
-        const bool not_origin = (i != remove_labels);
-
         // Look if there's NS or DNAME at this point of the tree, but ignore
         // Look if there's NS or DNAME at this point of the tree, but ignore
         // the NS RRs at the apex of the zone.
         // the NS RRs at the apex of the zone.
         const FoundRRsets found = getRRsets(superdomain.toText(),
         const FoundRRsets found = getRRsets(superdomain.toText(),
-                                            DELEGATION_TYPES(), not_origin);
+                                            DELEGATION_TYPES());
         if (found.first) {
         if (found.first) {
             // This node contains either NS or DNAME RRs so it does exist.
             // This node contains either NS or DNAME RRs so it does exist.
             const FoundIterator nsi(found.second.find(RRType::NS()));
             const FoundIterator nsi(found.second.find(RRType::NS()));
             const FoundIterator dni(found.second.find(RRType::DNAME()));
             const FoundIterator dni(found.second.find(RRType::DNAME()));
 
 
+            // Note if this is the origin. (We don't count NS records at the
+            // origin as a delegation so this controls whether NS RRs are
+            // included in the results of some searches.)
+            const bool not_origin = (i != remove_labels);
+
             // An optimisation.  We know that there is an exact match for
             // An optimisation.  We know that there is an exact match for
             // something at this point in the tree so remember it.  If we have
             // something at this point in the tree so remember it.  If we have
             // to do a wildcard search, as we search upwards through the tree
             // to do a wildcard search, as we search upwards through the tree
@@ -477,7 +466,7 @@ DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
             last_known = superdomain.getLabelCount();
             last_known = superdomain.getLabelCount();
 
 
             if (glue_ok && !first_ns && not_origin &&
             if (glue_ok && !first_ns && not_origin &&
-                    nsi != found.second.end()) {
+                nsi != found.second.end()) {
                 // If we are searching for glue ("glue OK" mode), store the
                 // If we are searching for glue ("glue OK" mode), store the
                 // highest NS record that we find that is not the apex.  This
                 // highest NS record that we find that is not the apex.  This
                 // is another optimisation for later, where we need the
                 // is another optimisation for later, where we need the
@@ -590,8 +579,9 @@ DatabaseClient::Finder::findWildcardMatch(
         // TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
         // TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
         // RFC 4592 section 4.4).
         // RFC 4592 section 4.4).
         // Search for a match.  The types are the same as with original query.
         // Search for a match.  The types are the same as with original query.
-        FoundRRsets found = getRRsets(wildcard, final_types, true,
+        const FoundRRsets found = getRRsets(wildcard, final_types,
-                                      &construct_name, type == RRType::ANY());
+                                            &construct_name,
+                                            type == RRType::ANY());
         if (found.first) {
         if (found.first) {
             // Found something - but what?
             // Found something - but what?
 
 
@@ -694,7 +684,7 @@ DatabaseClient::Finder::FindDNSSECContext::probe() {
             // such cases).
             // such cases).
             const string origin = finder_.getOrigin().toText();
             const string origin = finder_.getOrigin().toText();
             const FoundRRsets nsec3_found =
             const FoundRRsets nsec3_found =
-                finder_.getRRsets(origin, NSEC3PARAM_TYPES(), false);
+                finder_.getRRsets(origin, NSEC3PARAM_TYPES());
             const FoundIterator nfi=
             const FoundIterator nfi=
                 nsec3_found.second.find(RRType::NSEC3PARAM());
                 nsec3_found.second.find(RRType::NSEC3PARAM());
             is_nsec3_ = (nfi != nsec3_found.second.end());
             is_nsec3_ = (nfi != nsec3_found.second.end());
@@ -705,7 +695,7 @@ DatabaseClient::Finder::FindDNSSECContext::probe() {
             // described in Section 10.4 of RFC 5155.
             // described in Section 10.4 of RFC 5155.
             if (!is_nsec3_) {
             if (!is_nsec3_) {
                 const FoundRRsets nsec_found =
                 const FoundRRsets nsec_found =
-                    finder_.getRRsets(origin, NSEC_TYPES(), false);
+                    finder_.getRRsets(origin, NSEC_TYPES());
                 const FoundIterator nfi =
                 const FoundIterator nfi =
                     nsec_found.second.find(RRType::NSEC());
                     nsec_found.second.find(RRType::NSEC());
                 is_nsec_ = (nfi != nsec_found.second.end());
                 is_nsec_ = (nfi != nsec_found.second.end());
@@ -757,10 +747,8 @@ DatabaseClient::Finder::FindDNSSECContext::getDNSSECRRset(const Name &name,
     try {
     try {
         const Name& nsec_name =
         const Name& nsec_name =
             covering ? finder_.findPreviousName(name) : name;
             covering ? finder_.findPreviousName(name) : name;
-        const bool need_nscheck = (nsec_name != finder_.getOrigin());
         const FoundRRsets found = finder_.getRRsets(nsec_name.toText(),
         const FoundRRsets found = finder_.getRRsets(nsec_name.toText(),
-                                                    NSEC_TYPES(),
+                                                    NSEC_TYPES());
-                                                    need_nscheck);
         const FoundIterator nci = found.second.find(RRType::NSEC());
         const FoundIterator nci = found.second.find(RRType::NSEC());
         if (nci != found.second.end()) {
         if (nci != found.second.end()) {
             return (nci->second);
             return (nci->second);
@@ -984,16 +972,15 @@ DatabaseClient::Finder::findInternal(const Name& name, const RRType& type,
     // - Requested name is a delegation point (NS only but not at the zone
     // - Requested name is a delegation point (NS only but not at the zone
     //   apex - DNAME is ignored here as it redirects DNS names subordinate to
     //   apex - DNAME is ignored here as it redirects DNS names subordinate to
     //   the owner name - the owner name itself is not redirected.)
     //   the owner name - the owner name itself is not redirected.)
-    const bool is_origin = (name == getOrigin());
     WantedTypes final_types(FINAL_TYPES());
     WantedTypes final_types(FINAL_TYPES());
     final_types.insert(type);
     final_types.insert(type);
     const FoundRRsets found = getRRsets(name.toText(), final_types,
     const FoundRRsets found = getRRsets(name.toText(), final_types,
-                                        !is_origin, NULL,
+                                        NULL, type == RRType::ANY());
-                                        type == RRType::ANY());
     FindDNSSECContext dnssec_ctx(*this, options);
     FindDNSSECContext dnssec_ctx(*this, options);
     if (found.first) {
     if (found.first) {
         // Something found at the domain name.  Look into it further to get
         // Something found at the domain name.  Look into it further to get
         // the final result.
         // the final result.
+        const bool is_origin = (name == getOrigin());
         return (findOnNameResult(name, type, options, is_origin, found, NULL,
         return (findOnNameResult(name, type, options, is_origin, found, NULL,
                                  target, dnssec_ctx));
                                  target, dnssec_ctx));
     } else {
     } else {
@@ -1021,7 +1008,7 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
     // Now, we need to get the NSEC3 params from the apex and create the hash
     // Now, we need to get the NSEC3 params from the apex and create the hash
     // creator for it.
     // creator for it.
     const FoundRRsets nsec3param(getRRsets(getOrigin().toText(),
     const FoundRRsets nsec3param(getRRsets(getOrigin().toText(),
-                                 NSEC3PARAM_TYPES(), false));
+                                           NSEC3PARAM_TYPES()));
     const FoundIterator param(nsec3param.second.find(RRType::NSEC3PARAM()));
     const FoundIterator param(nsec3param.second.find(RRType::NSEC3PARAM()));
     if (!nsec3param.first || param == nsec3param.second.end()) {
     if (!nsec3param.first || param == nsec3param.second.end()) {
         // No NSEC3 params? :-(
         // No NSEC3 params? :-(
@@ -1061,7 +1048,7 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
         }
         }
 
 
         const FoundRRsets nsec3(getRRsets(hash + "." + otext, NSEC3_TYPES(),
         const FoundRRsets nsec3(getRRsets(hash + "." + otext, NSEC3_TYPES(),
-                                          false, NULL, false, context));
+                                          NULL, false, context));
 
 
         if (nsec3.first) {
         if (nsec3.first) {
             // We found an exact match against the current label.
             // We found an exact match against the current label.
@@ -1086,8 +1073,8 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
                 arg(labels).arg(prevHash);
                 arg(labels).arg(prevHash);
             context = accessor_->getNSEC3Records(prevHash, zone_id_);
             context = accessor_->getNSEC3Records(prevHash, zone_id_);
             const FoundRRsets prev_nsec3(getRRsets(prevHash + "." + otext,
             const FoundRRsets prev_nsec3(getRRsets(prevHash + "." + otext,
-                                                   NSEC3_TYPES(), false, NULL,
+                                                   NSEC3_TYPES(), NULL, false,
-                                                   false, context));
+                                                   context));
 
 
             if (!prev_nsec3.first) {
             if (!prev_nsec3.first) {
                 isc_throw(DataSourceError, "Hash " + prevHash + " returned "
                 isc_throw(DataSourceError, "Hash " + prevHash + " returned "

+ 6 - 9
src/lib/datasrc/database.h

@@ -963,17 +963,14 @@ public:
         ///
         ///
         /// \param name Which domain name should be scanned.
         /// \param name Which domain name should be scanned.
         /// \param types List of types the caller is interested in.
         /// \param types List of types the caller is interested in.
-        /// \param check_ns If this is set to true, it checks nothing lives
-        ///     together with NS record (with few little exceptions, like RRSIG
-        ///     or NSEC). This check is meant for non-apex NS records.
         /// \param construct_name If this is NULL, the resulting RRsets have
         /// \param construct_name If this is NULL, the resulting RRsets have
-        ///     their name set to name. If it is not NULL, it overrides the name
+        ///     their name set to name. If it is not NULL, it overrides the
-        ///     and uses this one (this can be used for wildcard synthesized
+        ///     name and uses this one (this can be used for wildcard
-        ///     records).
+        ///     synthesized records).
         /// \param any If this is true, it records all the types, not only the
         /// \param any If this is true, it records all the types, not only the
         ///     ones requested by types. It also puts a NULL pointer under the
         ///     ones requested by types. It also puts a NULL pointer under the
-        ///     ANY type into the result, if it finds any RRs at all, to easy the
+        ///     ANY type into the result, if it finds any RRs at all, to easy
-        ///     identification of success.
+        ///     the identification of success.
         /// \param srcContext This can be set to non-NULL value to override the
         /// \param srcContext This can be set to non-NULL value to override the
         ///     iterator context used for obtaining the data. This can be used,
         ///     iterator context used for obtaining the data. This can be used,
         ///     for example, to get data from the NSEC3 namespace.
         ///     for example, to get data from the NSEC3 namespace.
@@ -986,7 +983,7 @@ public:
         /// \throw DataSourceError If there's a low-level error with the
         /// \throw DataSourceError If there's a low-level error with the
         ///     database or the database contains bad data.
         ///     database or the database contains bad data.
         FoundRRsets getRRsets(const std::string& name,
         FoundRRsets getRRsets(const std::string& name,
-                              const WantedTypes& types, bool check_ns,
+                              const WantedTypes& types,
                               const std::string* construct_name = NULL,
                               const std::string* construct_name = NULL,
                               bool any = false,
                               bool any = false,
                               DatabaseAccessor::IteratorContextPtr srcContext =
                               DatabaseAccessor::IteratorContextPtr srcContext =

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

@@ -168,13 +168,16 @@ const char* const TEST_RECORDS[][5] = {
     {"child.insecdelegation.example.org.", "DS", "3600", "", "DS 5 3 3600 "
     {"child.insecdelegation.example.org.", "DS", "3600", "", "DS 5 3 3600 "
      "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
      "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
 
 
-    // Broken NS
+    // Delegation NS and other ordinary type of RR coexist at the same
+    // name.  This is deviant (except for some special cases like the other
+    // RR could be used for addressing the NS name), but as long as the
+    // other records are hidden behind the delegation for normal queries
+    // it's not necessarily harmful. (so "broken" may be too strong, but we
+    // keep the name since it could be in a chain of sorted names for DNSSEC
+    // processing and renaming them may have other bad effects for tests).
     {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
     {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
     {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
     {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
 
 
-    {"brokenns2.example.org.", "NS", "3600", "", "ns.example.com."},
-    {"brokenns2.example.org.", "A", "3600", "", "192.0.2.1"},
-
     // Now double DNAME, to test failure mode
     // Now double DNAME, to test failure mode
     {"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
     {"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
     {"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},
     {"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},
@@ -2202,15 +2205,23 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
                               ZoneFinder::FIND_DEFAULT),
                               ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
                  DataSourceError);
 
 
-    // Broken NS - it lives together with something else
+    // NS and other type coexist: deviant and not necessarily harmful.
-    EXPECT_THROW(finder->find(isc::dns::Name("brokenns1.example.org."),
+    // It should normally just result in DELEGATION; if GLUE_OK is specified,
-                              this->qtype_,
+    // the other RR should be visible.
-                              ZoneFinder::FIND_DEFAULT),
+    this->expected_rdatas_.clear();
-                 DataSourceError);
+    this->expected_rdatas_.push_back("ns.example.com");
-    EXPECT_THROW(finder->find(isc::dns::Name("brokenns2.example.org."),
+    doFindTest(*finder, Name("brokenns1.example.org"), this->qtype_,
-                              this->qtype_,
+               RRType::NS(), this->rrttl_, ZoneFinder::DELEGATION,
-                              ZoneFinder::FIND_DEFAULT),
+               this->expected_rdatas_, this->empty_rdatas_,
-                 DataSourceError);
+               ZoneFinder::RESULT_DEFAULT);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(*finder, Name("brokenns1.example.org"), this->qtype_,
+               this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->empty_rdatas_,
+               ZoneFinder::RESULT_DEFAULT, Name("brokenns1.example.org"),
+               ZoneFinder::FIND_GLUE_OK);
 }
 }
 
 
 TYPED_TEST(DatabaseClientTest, findDS) {
 TYPED_TEST(DatabaseClientTest, findDS) {

+ 0 - 13
src/lib/datasrc/tests/zone_finder_context_unittest.cc

@@ -208,13 +208,6 @@ TEST_P(ZoneFinderContextTest, getAdditionalDelegation) {
 TEST_P(ZoneFinderContextTest, getAdditionalDelegationAtZoneCut) {
 TEST_P(ZoneFinderContextTest, getAdditionalDelegationAtZoneCut) {
     // Similar to the previous case, but one of the NS addresses is at the
     // Similar to the previous case, but one of the NS addresses is at the
     // zone cut.
     // zone cut.
-
-    // XXX: the current database-based data source incorrectly rejects this
-    // setup (see #1771)
-    if (GetParam() == createSQLite3Client) {
-        return;
-    }
-
     ZoneFinderContextPtr ctx = finder_->find(Name("www.b.example.org"),
     ZoneFinderContextPtr ctx = finder_->find(Name("www.b.example.org"),
                                              RRType::SOA());
                                              RRType::SOA());
     EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
     EXPECT_EQ(ZoneFinder::DELEGATION, ctx->code);
@@ -316,12 +309,6 @@ TEST_P(ZoneFinderContextTest, getAdditionalMX) {
 }
 }
 
 
 TEST_P(ZoneFinderContextTest, getAdditionalMXAtZoneCut) {
 TEST_P(ZoneFinderContextTest, getAdditionalMXAtZoneCut) {
-    // XXX: the current database-based data source incorrectly rejects this
-    // setup (see #1771)
-    if (GetParam() == createSQLite3Client) {
-        return;
-    }
-
     ZoneFinderContextPtr ctx = finder_->find(Name("mxatcut.example.org."),
     ZoneFinderContextPtr ctx = finder_->find(Name("mxatcut.example.org."),
                                              RRType::MX());
                                              RRType::MX());
     EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
     EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);

+ 10 - 0
src/lib/datasrc/zone.h

@@ -376,6 +376,16 @@ public:
     ///   RRsets for that name are searched just like the normal case;
     ///   RRsets for that name are searched just like the normal case;
     ///   otherwise, if the search has encountered a zone cut, \c DELEGATION
     ///   otherwise, if the search has encountered a zone cut, \c DELEGATION
     ///   with the information of the highest zone cut will be returned.
     ///   with the information of the highest zone cut will be returned.
+    ///   Note: the term "glue" in the DNS protocol standard may sometimes
+    ///   cause confusion: some people use this term strictly for an address
+    ///   record (type AAAA or A) for the name used in the RDATA of an NS RR;
+    ///   some others seem to give it broader flexibility.  Nevertheless,
+    ///   in this API the "GLUE OK" simply means the search by find() can
+    ///   continue beyond a zone cut; the derived class implementation does
+    ///   not have to, and should not, check whether the type is an address
+    ///   record or whether the query name is pointed by some NS RR.
+    ///   It's up to the caller with which definition of "glue" the search
+    ///   result with this option should be used.
     /// - \c FIND_DNSSEC Request that DNSSEC data (like NSEC, RRSIGs) are
     /// - \c FIND_DNSSEC Request that DNSSEC data (like NSEC, RRSIGs) are
     ///   returned with the answer. It is allowed for the data source to
     ///   returned with the answer. It is allowed for the data source to
     ///   include them even when not requested.
     ///   include them even when not requested.

+ 8 - 8
src/lib/dns/labelsequence.cc

@@ -23,7 +23,7 @@
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 
 
-const char*
+const uint8_t*
 LabelSequence::getData(size_t *len) const {
 LabelSequence::getData(size_t *len) const {
     *len = getDataLength();
     *len = getDataLength();
     return (&name_.ndata_[name_.offsets_[first_label_]]);
     return (&name_.ndata_[name_.offsets_[first_label_]]);
@@ -47,22 +47,22 @@ LabelSequence::getDataLength() const {
 bool
 bool
 LabelSequence::equals(const LabelSequence& other, bool case_sensitive) const {
 LabelSequence::equals(const LabelSequence& other, bool case_sensitive) const {
     size_t len, other_len;
     size_t len, other_len;
-    const char* data = getData(&len);
+    const uint8_t* data = getData(&len);
-    const char* other_data = other.getData(&other_len);
+    const uint8_t* other_data = other.getData(&other_len);
 
 
     if (len != other_len) {
     if (len != other_len) {
         return (false);
         return (false);
     }
     }
     if (case_sensitive) {
     if (case_sensitive) {
-        return (std::strncmp(data, other_data, len) == 0);
+        return (std::memcmp(data, other_data, len) == 0);
     }
     }
 
 
     // As long as the data was originally validated as (part of) a name,
     // As long as the data was originally validated as (part of) a name,
     // label length must never be a capital ascii character, so we can
     // label length must never be a capital ascii character, so we can
     // simply compare them after converting to lower characters.
     // simply compare them after converting to lower characters.
     for (size_t i = 0; i < len; ++i) {
     for (size_t i = 0; i < len; ++i) {
-        const unsigned char ch = data[i];
+        const uint8_t ch = data[i];
-        const unsigned char other_ch = other_data[i];
+        const uint8_t other_ch = other_data[i];
         if (isc::dns::name::internal::maptolower[ch] !=
         if (isc::dns::name::internal::maptolower[ch] !=
             isc::dns::name::internal::maptolower[other_ch]) {
             isc::dns::name::internal::maptolower[other_ch]) {
             return (false);
             return (false);
@@ -97,14 +97,14 @@ LabelSequence::isAbsolute() const {
 size_t
 size_t
 LabelSequence::getHash(bool case_sensitive) const {
 LabelSequence::getHash(bool case_sensitive) const {
     size_t length;
     size_t length;
-    const char* s = getData(&length);
+    const uint8_t* s = getData(&length);
     if (length > 16) {
     if (length > 16) {
         length = 16;
         length = 16;
     }
     }
 
 
     size_t hash_val = 0;
     size_t hash_val = 0;
     while (length > 0) {
     while (length > 0) {
-        const unsigned char c = *s++;
+        const uint8_t c = *s++;
         boost::hash_combine(hash_val, case_sensitive ? c :
         boost::hash_combine(hash_val, case_sensitive ? c :
                             isc::dns::name::internal::maptolower[c]);
                             isc::dns::name::internal::maptolower[c]);
         --length;
         --length;

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

@@ -67,7 +67,7 @@ public:
     /// \param len Pointer to a size_t where the length of the data
     /// \param len Pointer to a size_t where the length of the data
     ///        will be stored (in number of octets)
     ///        will be stored (in number of octets)
     /// \return Pointer to the wire-format data of this label sequence
     /// \return Pointer to the wire-format data of this label sequence
-    const char* getData(size_t* len) const;
+    const uint8_t* getData(size_t* len) const;
 
 
     /// \brief Return the length of the wire-format data of this LabelSequence
     /// \brief Return the length of the wire-format data of this LabelSequence
     ///
     ///

+ 3 - 3
src/lib/dns/messagerenderer.cc

@@ -100,8 +100,8 @@ struct NameCompare {
         uint16_t item_label_len = 0;
         uint16_t item_label_len = 0;
         for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
         for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
             item_pos = nextPosition(*buffer_, item_pos, item_label_len);
             item_pos = nextPosition(*buffer_, item_pos, item_label_len);
-            const unsigned char ch1 = (*buffer_)[item_pos];
+            const uint8_t ch1 = (*buffer_)[item_pos];
-            const unsigned char ch2 = name_buf_->readUint8();
+            const uint8_t ch2 = name_buf_->readUint8();
             if (CASE_SENSITIVE) {
             if (CASE_SENSITIVE) {
                 if (ch1 != ch2) {
                 if (ch1 != ch2) {
                     return (false);
                     return (false);
@@ -293,7 +293,7 @@ MessageRenderer::writeName(const Name& name, const bool compress) {
     LabelSequence sequence(name);
     LabelSequence sequence(name);
     const size_t nlabels = sequence.getLabelCount();
     const size_t nlabels = sequence.getLabelCount();
     size_t data_len;
     size_t data_len;
-    const char* data;
+    const uint8_t* data;
 
 
     // Find the offset in the offset table whose name gives the longest
     // Find the offset in the offset table whose name gives the longest
     // match against the name to be rendered.
     // match against the name to be rendered.

+ 16 - 16
src/lib/dns/name.cc

@@ -75,7 +75,7 @@ const char digitvalue[256] = {
 
 
 namespace name {
 namespace name {
 namespace internal {
 namespace internal {
-const unsigned char maptolower[] = {
+const uint8_t maptolower[] = {
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
@@ -147,11 +147,11 @@ Name::Name(const std::string &namestring, bool downcase) {
     bool is_root = false;
     bool is_root = false;
     ft_state state = ft_init;
     ft_state state = ft_init;
 
 
-    std::vector<unsigned char> offsets;
+    NameOffsets offsets;
     offsets.reserve(Name::MAX_LABELS);
     offsets.reserve(Name::MAX_LABELS);
     offsets.push_back(0);
     offsets.push_back(0);
 
 
-    std::string ndata;
+    NameString ndata;
     ndata.reserve(Name::MAX_WIRE);
     ndata.reserve(Name::MAX_WIRE);
 
 
     // should we refactor this code using, e.g, the state pattern?  Probably
     // should we refactor this code using, e.g, the state pattern?  Probably
@@ -310,7 +310,7 @@ typedef enum {
 }
 }
 
 
 Name::Name(InputBuffer& buffer, bool downcase) {
 Name::Name(InputBuffer& buffer, bool downcase) {
-    std::vector<unsigned char> offsets;
+    NameOffsets offsets;
     offsets.reserve(Name::MAX_LABELS);
     offsets.reserve(Name::MAX_LABELS);
 
 
     /*
     /*
@@ -436,8 +436,8 @@ Name::toText(bool omit_final_dot) const {
         return (".");
         return (".");
     }
     }
 
 
-    std::string::const_iterator np = ndata_.begin();
+    NameString::const_iterator np = ndata_.begin();
-    std::string::const_iterator np_end = ndata_.end();
+    NameString::const_iterator np_end = ndata_.end();
     unsigned int labels = labelcount_; // use for integrity check
     unsigned int labels = labelcount_; // use for integrity check
     // init with an impossible value to catch error cases in the end:
     // init with an impossible value to catch error cases in the end:
     unsigned int count = MAX_LABELLEN + 1;
     unsigned int count = MAX_LABELLEN + 1;
@@ -467,7 +467,7 @@ Name::toText(bool omit_final_dot) const {
             }
             }
 
 
             while (count-- > 0) {
             while (count-- > 0) {
-                unsigned char c = *np++;
+                uint8_t c = *np++;
                 switch (c) {
                 switch (c) {
                 case 0x22: // '"'
                 case 0x22: // '"'
                 case 0x28: // '('
                 case 0x28: // '('
@@ -534,8 +534,8 @@ Name::compare(const Name& other) const {
         unsigned int count = (cdiff < 0) ? count1 : count2;
         unsigned int count = (cdiff < 0) ? count1 : count2;
 
 
         while (count > 0) {
         while (count > 0) {
-            unsigned char label1 = ndata_[pos1];
+            uint8_t label1 = ndata_[pos1];
-            unsigned char label2 = other.ndata_[pos2];
+            uint8_t label2 = other.ndata_[pos2];
 
 
             int chdiff = (int)maptolower[label1] - (int)maptolower[label2];
             int chdiff = (int)maptolower[label1] - (int)maptolower[label2];
             if (chdiff != 0) {
             if (chdiff != 0) {
@@ -571,15 +571,15 @@ Name::equals(const Name& other) const {
     }
     }
 
 
     for (unsigned int l = labelcount_, pos = 0; l > 0; --l) {
     for (unsigned int l = labelcount_, pos = 0; l > 0; --l) {
-        unsigned char count = ndata_[pos];
+        uint8_t count = ndata_[pos];
         if (count != other.ndata_[pos]) {
         if (count != other.ndata_[pos]) {
             return (false);
             return (false);
         }
         }
         ++pos;
         ++pos;
 
 
         while (count-- > 0) {
         while (count-- > 0) {
-            unsigned char label1 = ndata_[pos];
+            uint8_t label1 = ndata_[pos];
-            unsigned char label2 = other.ndata_[pos];
+            uint8_t label2 = other.ndata_[pos];
 
 
             if (maptolower[label1] != maptolower[label2]) {
             if (maptolower[label1] != maptolower[label2]) {
                 return (false);
                 return (false);
@@ -663,9 +663,9 @@ Name::reverse() const {
     retname.ndata_.reserve(length_);
     retname.ndata_.reserve(length_);
 
 
     // Copy the original name, label by label, from tail to head.
     // Copy the original name, label by label, from tail to head.
-    vector<unsigned char>::const_reverse_iterator rit0 = offsets_.rbegin();
+    NameOffsets::const_reverse_iterator rit0 = offsets_.rbegin();
-    vector<unsigned char>::const_reverse_iterator rit1 = rit0 + 1;
+    NameOffsets::const_reverse_iterator rit1 = rit0 + 1;
-    string::const_iterator n0 = ndata_.begin();
+    NameString::const_iterator n0 = ndata_.begin();
     retname.offsets_.push_back(0);
     retname.offsets_.push_back(0);
     while (rit1 != offsets_.rend()) {
     while (rit1 != offsets_.rend()) {
         retname.ndata_.append(n0 + *rit1, n0 + *rit0);
         retname.ndata_.append(n0 + *rit1, n0 + *rit0);
@@ -746,7 +746,7 @@ Name::downcase() {
 
 
         while (count > 0) {
         while (count > 0) {
             ndata_.at(pos) =
             ndata_.at(pos) =
-                maptolower[static_cast<unsigned char>(ndata_.at(pos))];
+                maptolower[ndata_.at(pos)];
             ++pos;
             ++pos;
             --nlen;
             --nlen;
             --count;
             --count;

+ 7 - 2
src/lib/dns/name.h

@@ -229,6 +229,11 @@ class Name {
     ///
     ///
     //@{
     //@{
 private:
 private:
+    /// \brief Name data string
+    typedef std::basic_string<uint8_t> NameString;
+    /// \brief Name offsets type
+    typedef std::vector<uint8_t> NameOffsets;
+
     /// The default constructor
     /// The default constructor
     ///
     ///
     /// This is used internally in the class implementation, but at least at
     /// This is used internally in the class implementation, but at least at
@@ -679,8 +684,8 @@ public:
     //@}
     //@}
 
 
 private:
 private:
-    std::string ndata_;
+    NameString ndata_;
-    std::vector<unsigned char> offsets_;
+    NameOffsets offsets_;
     unsigned int length_;
     unsigned int length_;
     unsigned int labelcount_;
     unsigned int labelcount_;
 };
 };

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

@@ -31,7 +31,7 @@ namespace isc {
 namespace dns {
 namespace dns {
 namespace name {
 namespace name {
 namespace internal {
 namespace internal {
-extern const unsigned char maptolower[];
+extern const uint8_t maptolower[];
 } // end of internal
 } // end of internal
 } // end of name
 } // end of name
 } // end of dns
 } // end of dns

+ 37 - 9
src/lib/dns/tests/labelsequence_unittest.cc

@@ -34,14 +34,21 @@ public:
                           n3("example.org"), n4("foo.bar.test.example"),
                           n3("example.org"), n4("foo.bar.test.example"),
                           n5("example.ORG"), n6("ExAmPlE.org"),
                           n5("example.ORG"), n6("ExAmPlE.org"),
                           n7("."), n8("foo.example.org.bar"),
                           n7("."), n8("foo.example.org.bar"),
+                          n9("\\000xample.org"),
+                          n10("\\000xample.org"),
+                          n11("\\000xample.com"),
+                          n12("\\000xamplE.com"),
                           ls1(n1), ls2(n2), ls3(n3), ls4(n4), ls5(n5),
                           ls1(n1), ls2(n2), ls3(n3), ls4(n4), ls5(n5),
-                          ls6(n6), ls7(n7), ls8(n8)
+                          ls6(n6), ls7(n7), ls8(n8),
+                          ls9(n9), ls10(n10), ls11(n11), ls12(n12)
     {};
     {};
     // Need to keep names in scope for at least the lifetime of
     // Need to keep names in scope for at least the lifetime of
     // the labelsequences
     // the labelsequences
     Name n1, n2, n3, n4, n5, n6, n7, n8;
     Name n1, n2, n3, n4, n5, n6, n7, n8;
+    Name n9, n10, n11, n12;
 
 
     LabelSequence ls1, ls2, ls3, ls4, ls5, ls6, ls7, ls8;
     LabelSequence ls1, ls2, ls3, ls4, ls5, ls6, ls7, ls8;
+    LabelSequence ls9, ls10, ls11, ls12;
 };
 };
 
 
 // Basic equality tests
 // Basic equality tests
@@ -81,6 +88,11 @@ TEST_F(LabelSequenceTest, equals_sensitive) {
     EXPECT_FALSE(ls5.equals(ls6, true));
     EXPECT_FALSE(ls5.equals(ls6, true));
     EXPECT_FALSE(ls5.equals(ls7, true));
     EXPECT_FALSE(ls5.equals(ls7, true));
     EXPECT_FALSE(ls5.equals(ls8, true));
     EXPECT_FALSE(ls5.equals(ls8, true));
+
+    EXPECT_TRUE(ls9.equals(ls10, true));
+    EXPECT_FALSE(ls9.equals(ls11, true));
+    EXPECT_FALSE(ls9.equals(ls12, true));
+    EXPECT_FALSE(ls11.equals(ls12, true));
 }
 }
 
 
 TEST_F(LabelSequenceTest, equals_insensitive) {
 TEST_F(LabelSequenceTest, equals_insensitive) {
@@ -123,28 +135,44 @@ TEST_F(LabelSequenceTest, equals_insensitive) {
     EXPECT_TRUE(ls5.equals(ls5));
     EXPECT_TRUE(ls5.equals(ls5));
     EXPECT_TRUE(ls5.equals(ls6));
     EXPECT_TRUE(ls5.equals(ls6));
     EXPECT_FALSE(ls5.equals(ls7));
     EXPECT_FALSE(ls5.equals(ls7));
+
+    EXPECT_TRUE(ls9.equals(ls10));
+    EXPECT_FALSE(ls9.equals(ls11));
+    EXPECT_FALSE(ls9.equals(ls12));
+    EXPECT_TRUE(ls11.equals(ls12));
 }
 }
 
 
 void
 void
-getDataCheck(const char* expected_data, size_t expected_len,
+getDataCheck(const uint8_t* expected_data, size_t expected_len,
              const LabelSequence& ls)
              const LabelSequence& ls)
 {
 {
     size_t len;
     size_t len;
-    const char* data = ls.getData(&len);
+    const uint8_t* data = ls.getData(&len);
     ASSERT_EQ(expected_len, len) << "Expected data: " << expected_data <<
     ASSERT_EQ(expected_len, len) << "Expected data: " << expected_data <<
                                     " name: " << ls.getName().toText();
                                     " name: " << ls.getName().toText();
     EXPECT_EQ(expected_len, ls.getDataLength()) <<
     EXPECT_EQ(expected_len, ls.getDataLength()) <<
         "Expected data: " << expected_data <<
         "Expected data: " << expected_data <<
         " name: " << ls.getName().toText();
         " name: " << ls.getName().toText();
     for (size_t i = 0; i < len; ++i) {
     for (size_t i = 0; i < len; ++i) {
-        EXPECT_EQ(expected_data[i], data[i]) << "Difference at pos " << i <<
+        EXPECT_EQ(expected_data[i], data[i]) <<
-                                                ": Expected data: " <<
+          "Difference at pos " << i << ": Expected data: " << expected_data <<
-                                                expected_data <<
+          " name: " << ls.getName().toText();;
-                                                " name: " <<
-                                                ls.getName().toText();;
     }
     }
 }
 }
 
 
+// Convenient data converter for expected data.  Label data must be of
+// uint8_t*, while it's convenient if we can specify some test data in
+// plain string (which is of char*).  This wrapper converts the latter to
+// the former in a safer way.
+void
+getDataCheck(const char* expected_char_data, size_t expected_len,
+             const LabelSequence& ls)
+{
+    const vector<uint8_t> expected_data(expected_char_data,
+                                        expected_char_data + expected_len);
+    getDataCheck(&expected_data[0], expected_len, ls);
+}
+
 TEST_F(LabelSequenceTest, getData) {
 TEST_F(LabelSequenceTest, getData) {
     getDataCheck("\007example\003org\000", 13, ls1);
     getDataCheck("\007example\003org\000", 13, ls1);
     getDataCheck("\007example\003com\000", 13, ls2);
     getDataCheck("\007example\003com\000", 13, ls2);
@@ -243,7 +271,7 @@ TEST_F(LabelSequenceTest, comparePart) {
 
 
     // Data comparison
     // Data comparison
     size_t len;
     size_t len;
-    const char* data = ls1.getData(&len);
+    const uint8_t* data = ls1.getData(&len);
     getDataCheck(data, len, ls8);
     getDataCheck(data, len, ls8);
 }
 }
 
 

+ 6 - 0
src/lib/python/isc/notify/notify_out.py

@@ -161,6 +161,12 @@ class NotifyOut:
             for item in slaves:
             for item in slaves:
                 self._notify_infos[zone_id].notify_slaves.append((item, 53))
                 self._notify_infos[zone_id].notify_slaves.append((item, 53))
 
 
+    def add_slave(self, address, port):
+        for zone_name, zone_class in sqlite3_ds.get_zones_info(self._db_file):
+            zone_id = (zone_name, zone_class)
+            if zone_id in self._notify_infos:
+                self._notify_infos[zone_id].notify_slaves.append((address, port))
+
     def send_notify(self, zone_name, zone_class='IN'):
     def send_notify(self, zone_name, zone_class='IN'):
         '''Send notify to one zone's slaves, this function is
         '''Send notify to one zone's slaves, this function is
         the only interface for class NotifyOut which can be called
         the only interface for class NotifyOut which can be called

+ 2 - 2
tests/lettuce/configurations/xfrin/inmem_slave.conf

@@ -19,8 +19,8 @@
             } ]
             } ]
         } ],
         } ],
         "listen_on": [ {
         "listen_on": [ {
-            "port": 47806,
+            "address": "::1",
-            "address": "127.0.0.1"
+            "port": 47806
         } ]
         } ]
     },
     },
     "Boss": {
     "Boss": {

+ 6 - 2
tests/lettuce/configurations/xfrin/retransfer_master.conf

@@ -10,13 +10,17 @@
     "Auth": {
     "Auth": {
         "database_file": "data/example.org.sqlite3",
         "database_file": "data/example.org.sqlite3",
         "listen_on": [ {
         "listen_on": [ {
-            "port": 47807,
+            "address": "::1",
-            "address": "::1"
+            "port": 47807
         } ]
         } ]
     },
     },
     "Xfrout": {
     "Xfrout": {
         "zone_config": [ {
         "zone_config": [ {
             "origin": "example.org"
             "origin": "example.org"
+        } ],
+        "also_notify": [ {
+            "address": "::1",
+            "port": 47806
         } ]
         } ]
     },
     },
     "Boss": {
     "Boss": {

+ 2 - 2
tests/lettuce/configurations/xfrin/retransfer_slave.conf

@@ -10,8 +10,8 @@
     "Auth": {
     "Auth": {
         "database_file": "data/test_nonexistent_db.sqlite3",
         "database_file": "data/test_nonexistent_db.sqlite3",
         "listen_on": [ {
         "listen_on": [ {
-            "port": 47806,
+            "address": "::1",
-            "address": "127.0.0.1"
+            "port": 47806
         } ]
         } ]
     },
     },
     "Boss": {
     "Boss": {

+ 38 - 0
tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf

@@ -0,0 +1,38 @@
+{
+    "version": 2,
+    "Logging": {
+        "loggers": [ {
+            "debuglevel": 99,
+            "severity": "DEBUG",
+            "name": "*"
+        } ]
+    },
+    "Auth": {
+        "database_file": "data/xfrin-notify.sqlite3",
+        "listen_on": [ {
+            "address": "::1",
+            "port": 47806
+        } ]
+    },
+    "Xfrin": {
+        "zones": [ {
+            "name": "example.org",
+            "master_addr": "::1",
+            "master_port": 47807
+        } ]
+    },
+    "Zonemgr": {
+        "secondary_zones": [ {
+            "name": "example.org",
+            "class": "IN"
+        } ]
+    },
+    "Boss": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+            "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}

+ 1 - 0
tests/lettuce/data/.gitignore

@@ -1,2 +1,3 @@
 /inmem-xfrin.sqlite3
 /inmem-xfrin.sqlite3
 /test_nonexistent_db.sqlite3
 /test_nonexistent_db.sqlite3
+/xfrin-notify.sqlite3

BIN
tests/lettuce/data/xfrin-notify.sqlite3.orig


+ 4 - 4
tests/lettuce/features/inmemory_over_sqlite3.feature

@@ -26,18 +26,18 @@ Feature: In-memory zone using SQLite3 backend
         And wait for bind10 stderr message XFRIN_STARTED
         And wait for bind10 stderr message XFRIN_STARTED
         And wait for bind10 stderr message ZONEMGR_STARTED
         And wait for bind10 stderr message ZONEMGR_STARTED
 
 
-        A query for www.example.org should have rcode NOERROR
+        A query for www.example.org to [::1]:47806 should have rcode NOERROR
         """
         """
         www.example.org.        3600    IN      A       192.0.2.63
         www.example.org.        3600    IN      A       192.0.2.63
         """
         """
-        A query for mail.example.org should have rcode NXDOMAIN
+        A query for mail.example.org to [::1]:47806 should have rcode NXDOMAIN
         When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
         When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
         Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
         Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
         Then wait for new bind10 stderr message AUTH_LOAD_ZONE
         Then wait for new bind10 stderr message AUTH_LOAD_ZONE
 
 
-        A query for www.example.org should have rcode NOERROR
+        A query for www.example.org to [::1]:47807 should have rcode NOERROR
         The answer section of the last query response should be
         The answer section of the last query response should be
         """
         """
         www.example.org.        3600    IN      A       192.0.2.1
         www.example.org.        3600    IN      A       192.0.2.1
         """
         """
-        A query for mail.example.org should have rcode NOERROR
+        A query for mail.example.org to [::1]:47806 should have rcode NOERROR

+ 2 - 2
tests/lettuce/features/terrain/bind10_control.py

@@ -306,8 +306,8 @@ def config_remove_command(step, name, value, cmdctl_port):
                 "quit"]
                 "quit"]
     run_bindctl(commands, cmdctl_port)
     run_bindctl(commands, cmdctl_port)
 
 
-@step('send bind10 the command (.+)(?: with cmdctl port (\d+))?')
+@step('send bind10(?: with cmdctl port (\d+))? the command (.+)')
-def send_command(step, command, cmdctl_port):
+def send_command(step, cmdctl_port, command):
     """
     """
     Run bindctl, send the given command, and exit bindctl.
     Run bindctl, send the given command, and exit bindctl.
     Parameters:
     Parameters:

+ 2 - 0
tests/lettuce/features/terrain/terrain.py

@@ -59,6 +59,8 @@ copylist = [
      "configurations/ddns/noddns.config"],
      "configurations/ddns/noddns.config"],
     ["data/inmem-xfrin.sqlite3.orig",
     ["data/inmem-xfrin.sqlite3.orig",
      "data/inmem-xfrin.sqlite3"],
      "data/inmem-xfrin.sqlite3"],
+    ["data/xfrin-notify.sqlite3.orig",
+     "data/xfrin-notify.sqlite3"],
     ["data/ddns/example.org.sqlite3.orig",
     ["data/ddns/example.org.sqlite3.orig",
      "data/ddns/example.org.sqlite3"]
      "data/ddns/example.org.sqlite3"]
 ]
 ]

+ 2 - 2
tests/lettuce/features/terrain/transfer.py

@@ -67,11 +67,11 @@ def perform_axfr(step, zone_name, address, port):
     Step definition:
     Step definition:
     An AXFR transfer of <zone_name> [from <address>:<port>]
     An AXFR transfer of <zone_name> [from <address>:<port>]
 
 
-    Address defaults to 127.0.0.1
+    Address defaults to ::1
     Port defaults to 47806
     Port defaults to 47806
     """
     """
     if address is None:
     if address is None:
-        address = "127.0.0.1"
+        address = "::1"
     # convert [IPv6_addr] to IPv6_addr:
     # convert [IPv6_addr] to IPv6_addr:
     address = re.sub(r"\[(.+)\]", r"\1", address)
     address = re.sub(r"\[(.+)\]", r"\1", address)
     if port is None:
     if port is None:

+ 2 - 2
tests/lettuce/features/xfrin_bind10.feature

@@ -23,11 +23,11 @@ Feature: Xfrin
     # Now we use the first step again to see if the file has been created
     # Now we use the first step again to see if the file has been created
     The file data/test_nonexistent_db.sqlite3 should exist
     The file data/test_nonexistent_db.sqlite3 should exist
 
 
-    A query for www.example.org should have rcode REFUSED
+    A query for www.example.org to [::1]:47806 should have rcode REFUSED
     When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
     When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
     Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
     Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
     Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
     Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
-    A query for www.example.org should have rcode NOERROR
+    A query for www.example.org to [::1]:47806 should have rcode NOERROR
 
 
     # The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
     # The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
     # The following check will get these by AXFR, so the total # of RRs
     # The following check will get these by AXFR, so the total # of RRs

+ 29 - 0
tests/lettuce/features/xfrin_notify_handling.feature

@@ -0,0 +1,29 @@
+Feature: Xfrin incoming notify handling
+    Tests for Xfrin incoming notify handling.
+
+    Scenario: Handle incoming notify
+    Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+    And wait for master stderr message BIND10_STARTED_CC
+    And wait for master stderr message CMDCTL_STARTED
+    And wait for master stderr message AUTH_SERVER_STARTED
+    And wait for master stderr message XFROUT_STARTED
+    And wait for master stderr message ZONEMGR_STARTED
+
+    And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+    And wait for bind10 stderr message BIND10_STARTED_CC
+    And wait for bind10 stderr message CMDCTL_STARTED
+    And wait for bind10 stderr message AUTH_SERVER_STARTED
+    And wait for bind10 stderr message XFRIN_STARTED
+    And wait for bind10 stderr message ZONEMGR_STARTED
+
+    A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+    When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+    Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+    Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+    Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+
+    A query for www.example.org to [::1]:47806 should have rcode NOERROR