]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
4082. [bug] Incrementally sign large inline zone deltas.
authorMark Andrews <marka@isc.org>
Wed, 4 Mar 2015 22:59:29 +0000 (09:59 +1100)
committerMark Andrews <marka@isc.org>
Wed, 4 Mar 2015 22:59:29 +0000 (09:59 +1100)
                        [RT #37927]

CHANGES
bin/nsupdate/nsupdate.c
bin/tests/system/inline/clean.sh
bin/tests/system/inline/ns3/named.conf
doc/arm/notes.xml
lib/dns/geoip.c
lib/dns/include/dns/types.h
lib/dns/include/dns/update.h
lib/dns/include/dns/zone.h
lib/dns/update.c
lib/dns/zone.c

diff --git a/CHANGES b/CHANGES
index 8cdb86d338369d799a2c07e2eeee847f0cc2b98e..0ee653891df6def2ca3033ea86d492768eb856c5 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+4082.  [bug]           Incrementally sign large inline zone deltas.
+                       [RT #37927]
+
 4081.  [cleanup]       Use dns_rdatalist_init consistently. [RT #38759]
 
 4080.  [func]          Completed change #4022, adding a "lock-file" option
index 305a9035e08f4559529673a560748187fc163950..716932db18ca867bd37dc257f70a4ada59fa0d25 100644 (file)
@@ -2276,6 +2276,7 @@ update_completed(isc_task_t *task, isc_event_t *event) {
                dns_request_destroy(&request);
                dns_message_renderreset(updatemsg);
                dns_message_settsigkey(updatemsg, NULL);
+               /* XXX MPA fix zonename is freed already */
                send_update(zname, &master_servers[master_inuse]);
                isc_event_free(&event);
                return;
@@ -2578,6 +2579,9 @@ recvsoa(isc_task_t *task, isc_event_t *event) {
        dns_name_init(&master, NULL);
        dns_name_clone(&soa.origin, &master);
 
+       /*
+        * XXXMPA
+        */
        if (userzone != NULL)
                zname = userzone;
        else
index 3fb2ffd1cbf449e77e70567139b96cb6544dbefc..3611f9cb45565ed7e9e48f09f4256d0523631ff9 100644 (file)
@@ -89,3 +89,4 @@ rm -f import.key Kimport*
 rm -f checkgost checkdsa checkecdsa
 rm -f ns3/a-file
 rm -f ns*/named.lock
+rm -f dig.out.*
index 60635696894132a9a6a6d12a87ecd723bd7d470f..79c87afa722163528bf0bb49ca1dfa55ae07fdf0 100644 (file)
@@ -44,6 +44,7 @@ zone "bits" {
        auto-dnssec maintain;
        allow-update-forwarding { any; };
        file "bits.bk";
+       sig-signing-signatures 1;       // force incremental processing
 };
 
 server 10.53.0.4 { request-ixfr no; };
index d91ba7dff88323333607c2a8b11f20b0da630959..8bcc72528c44cb5075b4b9aebe565379678e7d87 100644 (file)
          blocked.
        </para>
       </listitem>
+      <listitem>
+       <para>
+         Large inline-signing changes should be less disruptive.
+         Signature generation is now done incrementally; the number
+         of signatures to be generated in each quantum is controlled
+         by "sig-signing-signatures <replaceable>number</replaceable>;".
+         [RT #37927]
+       </para>
+      </listitem>
     </itemizedlist>
   </sect2>
   <sect2 id="relnotes_bugs">
index 4e9988710d978401ac35aa9525dd639f03cd649e..40a5c0824033aa5294898897a915c3b3e1b7772c 100644 (file)
@@ -168,7 +168,6 @@ set_state(unsigned int family, isc_uint32_t ipnum, const geoipv6_t *ipnum6,
          GeoIPRegion *region, char *name, const char *text, int id)
 {
        geoip_state_t *state = NULL;
-
 #ifdef ISC_PLATFORM_USETHREADS
        isc_result_t result;
 
index d30e6b003e5ac7be91d4f83a46a5cd65f3e3f91e..62dd810607aa5de3a5bc70b95a3be39f7b452ed0 100644 (file)
@@ -133,6 +133,7 @@ typedef struct dns_tsec                             dns_tsec_t;
 typedef struct dns_tsig_keyring                        dns_tsig_keyring_t;
 typedef struct dns_tsigkey                     dns_tsigkey_t;
 typedef isc_uint32_t                           dns_ttl_t;
+typedef struct dns_update_state                        dns_update_state_t;
 typedef struct dns_validator                   dns_validator_t;
 typedef struct dns_view                                dns_view_t;
 typedef ISC_LIST(dns_view_t)                   dns_viewlist_t;
index 2d2c491f38a3ae6c0d8ee6cb897493703f931215..4d3ec9a986cf2464fc6db07a089d6ea712f01c2a 100644 (file)
@@ -59,6 +59,12 @@ dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
                      dns_dbversion_t *oldver, dns_dbversion_t *newver,
                      dns_diff_t *diff, isc_uint32_t sigvalidityinterval);
 
+isc_result_t
+dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+                        dns_dbversion_t *oldver, dns_dbversion_t *newver,
+                        dns_diff_t *diff, isc_uint32_t sigvalidityinterval,
+                        dns_update_state_t **state);
+
 ISC_LANG_ENDDECLS
 
 #endif /* DNS_UPDATE_H */
index 3fb2e6198bbebe18ae7452bcf64d55c5836bc2ea..5674f427b45d1ec328fa3f0919b285368cfe853d 100644 (file)
@@ -2102,6 +2102,12 @@ dns_zone_setsignatures(dns_zone_t *zone, isc_uint32_t signatures);
  * Set the number of signatures that will be generated per quantum.
  */
 
+isc_uint32_t
+dns_zone_getsignatures(dns_zone_t *zone);
+/*%<
+ * Get the number of signatures that will be generated per quantum.
+ */
+
 isc_result_t
 dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm,
                     isc_uint16_t keyid, isc_boolean_t deleteit);
index 5950f42b63d22b10b1955f41d9073ae088eca366..294c9d18c25dd69494607384d2f0b50382c9b4ef 100644 (file)
@@ -19,6 +19,8 @@
 #include <time.h>
 
 #include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
 #include <isc/netaddr.h>
 #include <isc/print.h>
 #include <isc/serial.h>
@@ -59,6 +61,9 @@
 
 /**************************************************************************/
 
+#define STATE_MAGIC                    ISC_MAGIC('S', 'T', 'T', 'E')
+#define DNS_STATE_VALID(state)         ISC_MAGIC_VALID(state, STATE_MAGIC)
+
 /*%
  * Log level for tracing dynamic update protocol requests.
  */
@@ -1251,7 +1256,8 @@ add_exposed_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
                 dns_dbversion_t *ver, dns_name_t *name, isc_boolean_t cut,
                 dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys,
                 isc_stdtime_t inception, isc_stdtime_t expire,
-                isc_boolean_t check_ksk, isc_boolean_t keyset_kskonly)
+                isc_boolean_t check_ksk, isc_boolean_t keyset_kskonly,
+                unsigned int *sigs)
 {
        isc_result_t result;
        dns_dbnode_t *node;
@@ -1297,10 +1303,11 @@ add_exposed_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
                if (flag)
                        continue;;
                result = add_sigs(log, zone, db, ver, name, type, diff,
-                                         keys, nkeys, inception, expire,
-                                         check_ksk, keyset_kskonly);
+                                 keys, nkeys, inception, expire,
+                                 check_ksk, keyset_kskonly);
                if (result != ISC_R_SUCCESS)
                        goto cleanup_iterator;
+               (*sigs)++;
        }
        if (result == ISC_R_NOMORE)
                result = ISC_R_SUCCESS;
@@ -1330,519 +1337,684 @@ dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
                      dns_dbversion_t *oldver, dns_dbversion_t *newver,
                      dns_diff_t *diff, isc_uint32_t sigvalidityinterval)
 {
-       isc_result_t result;
-       dns_difftuple_t *t;
+       return (dns_update_signaturesinc(log, zone, db, oldver, newver, diff,
+                                        sigvalidityinterval, NULL));
+}
+
+struct dns_update_state {
+       unsigned int magic;
        dns_diff_t diffnames;
        dns_diff_t affected;
        dns_diff_t sig_diff;
        dns_diff_t nsec_diff;
        dns_diff_t nsec_mindiff;
-       isc_boolean_t flag, build_nsec, build_nsec3;
+       dns_diff_t work;
        dst_key_t *zone_keys[DNS_MAXZONEKEYS];
-       unsigned int nkeys = 0;
-       unsigned int i;
-       isc_stdtime_t now, inception, expire;
+       unsigned int nkeys;
+       isc_stdtime_t inception, expire;
        dns_ttl_t nsecttl;
+       isc_boolean_t check_ksk, keyset_kskonly;
+       enum { sign_updates, remove_orphaned, build_chain, process_nsec,
+              sign_nsec, update_nsec3, process_nsec3, sign_nsec3 } state;
+};
+
+isc_result_t
+dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
+                        dns_dbversion_t *oldver, dns_dbversion_t *newver,
+                        dns_diff_t *diff, isc_uint32_t sigvalidityinterval,
+                        dns_update_state_t **statep)
+{
+       isc_result_t result = ISC_R_SUCCESS;
+       dns_update_state_t mystate, *state;
+
+       dns_difftuple_t *t, *next;
+       isc_boolean_t flag, build_nsec, build_nsec3;
+       unsigned int i;
+       isc_stdtime_t now;
        dns_rdata_soa_t soa;
        dns_rdata_t rdata = DNS_RDATA_INIT;
        dns_rdataset_t rdataset;
        dns_dbnode_t *node = NULL;
-       isc_boolean_t check_ksk, keyset_kskonly;
        isc_boolean_t unsecure;
        isc_boolean_t cut;
        dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+       unsigned int sigs = 0;
+       unsigned int maxsigs = dns_zone_getsignatures(zone);
 
-       dns_diff_init(diff->mctx, &diffnames);
-       dns_diff_init(diff->mctx, &affected);
-
-       dns_diff_init(diff->mctx, &sig_diff);
-       dns_diff_init(diff->mctx, &nsec_diff);
-       dns_diff_init(diff->mctx, &nsec_mindiff);
-
-       result = find_zone_keys(zone, db, newver, diff->mctx,
-                               DNS_MAXZONEKEYS, zone_keys, &nkeys);
-       if (result != ISC_R_SUCCESS) {
-               update_log(log, zone, ISC_LOG_ERROR,
-                          "could not get zone keys for secure dynamic update");
-               goto failure;
-       }
-
-       isc_stdtime_get(&now);
-       inception = now - 3600; /* Allow for some clock skew. */
-       expire = now + sigvalidityinterval;
-
-       /*
-        * Do we look at the KSK flag on the DNSKEY to determining which
-        * keys sign which RRsets?  First check the zone option then
-        * check the keys flags to make sure at least one has a ksk set
-        * and one doesn't.
-        */
-       check_ksk = ISC_TF((dns_zone_getoptions(zone) &
-                           DNS_ZONEOPT_UPDATECHECKKSK) != 0);
-       keyset_kskonly = ISC_TF((dns_zone_getoptions(zone) &
-                               DNS_ZONEOPT_DNSKEYKSKONLY) != 0);
-
-       /*
-        * Get the NSEC/NSEC3 TTL from the SOA MINIMUM field.
-        */
-       CHECK(dns_db_findnode(db, dns_db_origin(db), ISC_FALSE, &node));
-       dns_rdataset_init(&rdataset);
-       CHECK(dns_db_findrdataset(db, node, newver, dns_rdatatype_soa, 0,
-                                 (isc_stdtime_t) 0, &rdataset, NULL));
-       CHECK(dns_rdataset_first(&rdataset));
-       dns_rdataset_current(&rdataset, &rdata);
-       CHECK(dns_rdata_tostruct(&rdata, &soa, NULL));
-       nsecttl = soa.minimum;
-       dns_rdataset_disassociate(&rdataset);
-       dns_db_detachnode(db, &node);
-
-       /*
-        * Find all RRsets directly affected by the update, and
-        * update their RRSIGs.  Also build a list of names affected
-        * by the update in "diffnames".
-        */
-       CHECK(dns_diff_sort(diff, temp_order));
+       if (statep == NULL || (statep != NULL && *statep == NULL)) {
+               if (statep == NULL) {
+                       state = &mystate;
+               } else {
+                       state = isc_mem_get(diff->mctx, sizeof(*state));
+                       if (state == NULL)
+                               return (ISC_R_NOMEMORY);
+               }
 
-       t = ISC_LIST_HEAD(diff->tuples);
-       while (t != NULL) {
-               dns_name_t *name = &t->name;
-               /* Now "name" is a new, unique name affected by the update. */
+               dns_diff_init(diff->mctx, &state->diffnames);
+               dns_diff_init(diff->mctx, &state->affected);
+               dns_diff_init(diff->mctx, &state->sig_diff);
+               dns_diff_init(diff->mctx, &state->nsec_diff);
+               dns_diff_init(diff->mctx, &state->nsec_mindiff);
+               dns_diff_init(diff->mctx, &state->work);
+               state->nkeys = 0;
+
+               result = find_zone_keys(zone, db, newver, diff->mctx,
+                                       DNS_MAXZONEKEYS, state->zone_keys,
+                                       &state->nkeys);
+               if (result != ISC_R_SUCCESS) {
+                       update_log(log, zone, ISC_LOG_ERROR,
+                                  "could not get zone keys for secure "
+                                  "dynamic update");
+                       goto failure;
+               }
 
-               CHECK(namelist_append_name(&diffnames, name));
+               isc_stdtime_get(&now);
+               state->inception = now - 3600; /* Allow for some clock skew. */
+               state->expire = now + sigvalidityinterval;
 
-               while (t != NULL && dns_name_equal(&t->name, name)) {
-                       dns_rdatatype_t type;
-                       type = t->rdata.type;
+               /*
+                * Do we look at the KSK flag on the DNSKEY to determining which
+                * keys sign which RRsets?  First check the zone option then
+                * check the keys flags to make sure at least one has a ksk set
+                * and one doesn't.
+                */
+               state->check_ksk = ISC_TF((dns_zone_getoptions(zone) &
+                                   DNS_ZONEOPT_UPDATECHECKKSK) != 0);
+               state->keyset_kskonly = ISC_TF((dns_zone_getoptions(zone) &
+                                       DNS_ZONEOPT_DNSKEYKSKONLY) != 0);
 
-                       /*
-                        * Now "name" and "type" denote a new unique RRset
-                        * affected by the update.
-                        */
+               /*
+                * Get the NSEC/NSEC3 TTL from the SOA MINIMUM field.
+                */
+               CHECK(dns_db_findnode(db, dns_db_origin(db), ISC_FALSE, &node));
+               dns_rdataset_init(&rdataset);
+               CHECK(dns_db_findrdataset(db, node, newver, dns_rdatatype_soa,
+                                         0, (isc_stdtime_t) 0, &rdataset,
+                                         NULL));
+               CHECK(dns_rdataset_first(&rdataset));
+               dns_rdataset_current(&rdataset, &rdata);
+               CHECK(dns_rdata_tostruct(&rdata, &soa, NULL));
+               state->nsecttl = soa.minimum;
+               dns_rdataset_disassociate(&rdataset);
+               dns_db_detachnode(db, &node);
 
-                       /* Don't sign RRSIGs. */
-                       if (type == dns_rdatatype_rrsig)
-                               goto skip;
+               /*
+                * Find all RRsets directly affected by the update, and
+                * update their RRSIGs.  Also build a list of names affected
+                * by the update in "diffnames".
+                */
+               CHECK(dns_diff_sort(diff, temp_order));
+               state->state = sign_updates;
+               state->magic = STATE_MAGIC;
+               if (statep != NULL)
+                       *statep = state;
+       } else {
+               REQUIRE(DNS_STATE_VALID(*statep));
+               state = *statep;
+       }
 
+ next_state:
+       switch (state->state) {
+       case sign_updates:
+               t = ISC_LIST_HEAD(diff->tuples);
+               while (t != NULL) {
+                       dns_name_t *name = &t->name;
                        /*
-                        * Delete all old RRSIGs covering this type, since they
-                        * are all invalid when the signed RRset has changed.
-                        * We may not be able to recreate all of them - tough.
-                        * Special case changes to the zone's DNSKEY records
-                        * to support offline KSKs.
+                        * Now "name" is a new, unique name affected by the
+                        * update.
                         */
-                       if (type == dns_rdatatype_dnskey)
-                               del_keysigs(db, newver, name, &sig_diff,
-                                           zone_keys, nkeys);
-                       else
-                               CHECK(delete_if(true_p, db, newver, name,
-                                               dns_rdatatype_rrsig, type,
-                                               NULL, &sig_diff));
 
-                       /*
-                        * If this RRset is still visible after the update,
-                        * add a new signature for it.
-                        */
-                       CHECK(rrset_visible(db, newver, name, type, &flag));
-                       if (flag) {
-                               CHECK(add_sigs(log, zone, db, newver, name,
-                                              type, &sig_diff, zone_keys,
-                                              nkeys, inception, expire,
-                                              check_ksk, keyset_kskonly));
-                       }
-               skip:
-                       /* Skip any other updates to the same RRset. */
-                       while (t != NULL &&
-                              dns_name_equal(&t->name, name) &&
-                              t->rdata.type == type)
-                       {
-                               t = ISC_LIST_NEXT(t, link);
+                       CHECK(namelist_append_name(&state->diffnames, name));
+
+                       while (t != NULL && dns_name_equal(&t->name, name)) {
+                               dns_rdatatype_t type;
+                               type = t->rdata.type;
+
+                               /*
+                                * Now "name" and "type" denote a new unique
+                                * RRset affected by the update.
+                                */
+
+                               /* Don't sign RRSIGs. */
+                               if (type == dns_rdatatype_rrsig)
+                                       goto skip;
+
+                               /*
+                                * Delete all old RRSIGs covering this type,
+                                * since they are all invalid when the signed
+                                * RRset has changed.  We may not be able to
+                                * recreate all of them - tough.
+                                * Special case changes to the zone's DNSKEY
+                                * records to support offline KSKs.
+                                */
+                               if (type == dns_rdatatype_dnskey)
+                                       del_keysigs(db, newver, name,
+                                                   &state->sig_diff,
+                                                   state->zone_keys,
+                                                   state->nkeys);
+                               else
+                                       CHECK(delete_if(true_p, db, newver,
+                                                       name,
+                                                       dns_rdatatype_rrsig,
+                                                       type, NULL,
+                                                       &state->sig_diff));
+
+                               /*
+                                * If this RRset is still visible after the
+                                * update, add a new signature for it.
+                                */
+                               CHECK(rrset_visible(db, newver, name, type,
+                                                  &flag));
+                               if (flag) {
+                                       CHECK(add_sigs(log, zone, db, newver,
+                                                      name, type,
+                                                      &state->sig_diff,
+                                                      state->zone_keys,
+                                                      state->nkeys,
+                                                      state->inception,
+                                                      state->expire,
+                                                      state->check_ksk,
+                                                      state->keyset_kskonly));
+                                       sigs++;
+                               }
+                       skip:
+                               /* Skip any other updates to the same RRset. */
+                               while (t != NULL &&
+                                      dns_name_equal(&t->name, name) &&
+                                      t->rdata.type == type)
+                               {
+                                       next = ISC_LIST_NEXT(t, link);
+                                       ISC_LIST_UNLINK(diff->tuples, t, link);
+                                       ISC_LIST_APPEND(state->work.tuples, t,
+                                                       link);
+                                       t = next;
+                               }
                        }
+                       if (state != &mystate && sigs > maxsigs)
+                               return (DNS_R_CONTINUE);
                }
-       }
-       update_log(log, zone, ISC_LOG_DEBUG(3), "updated data signatures");
+               ISC_LIST_APPENDLIST(diff->tuples, state->work.tuples, link);
 
-       /* Remove orphaned NSECs and RRSIG NSECs. */
-       for (t = ISC_LIST_HEAD(diffnames.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               CHECK(non_nsec_rrset_exists(db, newver, &t->name, &flag));
-               if (! flag) {
-                       CHECK(delete_if(true_p, db, newver, &t->name,
-                                       dns_rdatatype_any, 0,
-                                       NULL, &sig_diff));
+               update_log(log, zone, ISC_LOG_DEBUG(3),
+                          "updated data signatures");
+               /*FALLTHROUGH*/
+       case remove_orphaned:
+               state->state = remove_orphaned;
+
+               /* Remove orphaned NSECs and RRSIG NSECs. */
+               for (t = ISC_LIST_HEAD(state->diffnames.tuples);
+                    t != NULL;
+                    t = ISC_LIST_NEXT(t, link))
+               {
+                       CHECK(non_nsec_rrset_exists(db, newver,
+                                                   &t->name, &flag));
+                       if (!flag) {
+                               CHECK(delete_if(true_p, db, newver, &t->name,
+                                               dns_rdatatype_any, 0,
+                                               NULL, &state->sig_diff));
+                       }
                }
-       }
-       update_log(log, zone, ISC_LOG_DEBUG(3),
-                  "removed any orphaned NSEC records");
-
-       /*
-        * See if we need to build NSEC or NSEC3 chains.
-        */
-       CHECK(dns_private_chains(db, newver, privatetype, &build_nsec,
-                                &build_nsec3));
-       if (!build_nsec)
-               goto update_nsec3;
-
-       update_log(log, zone, ISC_LOG_DEBUG(3), "rebuilding NSEC chain");
-
-       /*
-        * When a name is created or deleted, its predecessor needs to
-        * have its NSEC updated.
-        */
-       for (t = ISC_LIST_HEAD(diffnames.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               isc_boolean_t existed, exists;
-               dns_fixedname_t fixedname;
-               dns_name_t *prevname;
-
-               dns_fixedname_init(&fixedname);
-               prevname = dns_fixedname_name(&fixedname);
-
-               if (oldver != NULL)
-                       CHECK(name_exists(db, oldver, &t->name, &existed));
-               else
-                       existed = ISC_FALSE;
-               CHECK(name_exists(db, newver, &t->name, &exists));
-               if (exists == existed)
-                       continue;
+               update_log(log, zone, ISC_LOG_DEBUG(3),
+                          "removed any orphaned NSEC records");
 
                /*
-                * Find the predecessor.
-                * When names become obscured or unobscured in this update
-                * transaction, we may find the wrong predecessor because
-                * the NSECs have not yet been updated to reflect the delegation
-                * change.  This should not matter because in this case,
-                * the correct predecessor is either the delegation node or
-                * a newly unobscured node, and those nodes are on the
-                * "affected" list in any case.
+                * See if we need to build NSEC or NSEC3 chains.
                 */
-               CHECK(next_active(log, zone, db, newver,
-                                 &t->name, prevname, ISC_FALSE));
-               CHECK(namelist_append_name(&affected, prevname));
-       }
+               CHECK(dns_private_chains(db, newver, privatetype, &build_nsec,
+                                        &build_nsec3));
+               if (!build_nsec) {
+                       state->state = update_nsec3;
+                       goto next_state;
+               }
 
-       /*
-        * Find names potentially affected by delegation changes
-        * (obscured by adding an NS or DNAME, or unobscured by
-        * removing one).
-        */
-       for (t = ISC_LIST_HEAD(diffnames.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               isc_boolean_t ns_existed, dname_existed;
-               isc_boolean_t ns_exists, dname_exists;
+               update_log(log, zone, ISC_LOG_DEBUG(3),
+                          "rebuilding NSEC chain");
 
-               if (oldver != NULL)
-                       CHECK(rrset_exists(db, oldver, &t->name,
-                                         dns_rdatatype_ns, 0, &ns_existed));
-               else
-                       ns_existed = ISC_FALSE;
-               if (oldver != NULL)
-                       CHECK(rrset_exists(db, oldver, &t->name,
-                                          dns_rdatatype_dname, 0,
-                                          &dname_existed));
-               else
-                       dname_existed = ISC_FALSE;
-               CHECK(rrset_exists(db, newver, &t->name, dns_rdatatype_ns, 0,
-                                  &ns_exists));
-               CHECK(rrset_exists(db, newver, &t->name, dns_rdatatype_dname, 0,
-                                  &dname_exists));
-               if ((ns_exists || dname_exists) == (ns_existed || dname_existed))
-                       continue;
+               /*FALLTHROUGH*/
+       case build_chain:
+               state->state = build_chain;
                /*
-                * There was a delegation change.  Mark all subdomains
-                * of t->name as potentially needing a NSEC update.
+                * When a name is created or deleted, its predecessor needs to
+                * have its NSEC updated.
                 */
-               CHECK(namelist_append_subdomain(db, &t->name, &affected));
-       }
-
-       ISC_LIST_APPENDLIST(affected.tuples, diffnames.tuples, link);
-       INSIST(ISC_LIST_EMPTY(diffnames.tuples));
-
-       CHECK(uniqify_name_list(&affected));
-
-       /*
-        * Determine which names should have NSECs, and delete/create
-        * NSECs to make it so.  We don't know the final NSEC targets yet,
-        * so we just create placeholder NSECs with arbitrary contents
-        * to indicate that their respective owner names should be part of
-        * the NSEC chain.
-        */
-       for (t = ISC_LIST_HEAD(affected.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               isc_boolean_t exists;
-               dns_name_t *name = &t->name;
+               for (t = ISC_LIST_HEAD(state->diffnames.tuples);
+                    t != NULL;
+                    t = ISC_LIST_NEXT(t, link))
+               {
+                       isc_boolean_t existed, exists;
+                       dns_fixedname_t fixedname;
+                       dns_name_t *prevname;
+
+                       dns_fixedname_init(&fixedname);
+                       prevname = dns_fixedname_name(&fixedname);
+
+                       if (oldver != NULL)
+                               CHECK(name_exists(db, oldver, &t->name,
+                                                 &existed));
+                       else
+                               existed = ISC_FALSE;
+                       CHECK(name_exists(db, newver, &t->name, &exists));
+                       if (exists == existed)
+                               continue;
 
-               CHECK(name_exists(db, newver, name, &exists));
-               if (! exists)
-                       continue;
-               CHECK(is_active(db, newver, name, &flag, &cut, NULL));
-               if (!flag) {
-                       /*
-                        * This name is obscured.  Delete any
-                        * existing NSEC record.
-                        */
-                       CHECK(delete_if(true_p, db, newver, name,
-                                       dns_rdatatype_nsec, 0,
-                                       NULL, &nsec_diff));
-                       CHECK(delete_if(rrsig_p, db, newver, name,
-                                       dns_rdatatype_any, 0, NULL, diff));
-               } else {
                        /*
-                        * This name is not obscured.  It needs to have a
-                        * NSEC unless it is the at the origin, in which
-                        * case it should already exist if there is a complete
-                        * NSEC chain and if there isn't a complete NSEC chain
-                        * we don't want to add one as that would signal that
-                        * there is a complete NSEC chain.
+                        * Find the predecessor.
+                        * When names become obscured or unobscured in this
+                        * update transaction, we may find the wrong
+                        * predecessor because the NSECs have not yet been
+                        * updated to reflect the delegation change.  This
+                        * should not matter because in this case, the correct
+                        * predecessor is either the delegation node or a 
+                        * newly unobscured node, and those nodes are on the
+                        * "affected" list in any case.
                         */
-                       if (!dns_name_equal(name, dns_db_origin(db))) {
-                               CHECK(rrset_exists(db, newver, name,
-                                                  dns_rdatatype_nsec, 0,
-                                                  &flag));
-                               if (!flag)
-                                       CHECK(add_placeholder_nsec(db, newver,
-                                                                  name, diff));
-                       }
-                       CHECK(add_exposed_sigs(log, zone, db, newver, name,
-                                              cut, &sig_diff, zone_keys, nkeys,
-                                              inception, expire, check_ksk,
-                                              keyset_kskonly));
+                       CHECK(next_active(log, zone, db, newver,
+                                         &t->name, prevname, ISC_FALSE));
+                       CHECK(namelist_append_name(&state->affected, prevname));
                }
-       }
 
-       /*
-        * Now we know which names are part of the NSEC chain.
-        * Make them all point at their correct targets.
-        */
-       for (t = ISC_LIST_HEAD(affected.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               CHECK(rrset_exists(db, newver, &t->name,
-                                  dns_rdatatype_nsec, 0, &flag));
-               if (flag) {
+               /*
+                * Find names potentially affected by delegation changes
+                * (obscured by adding an NS or DNAME, or unobscured by
+                * removing one).
+                */
+               for (t = ISC_LIST_HEAD(state->diffnames.tuples);
+                    t != NULL;
+                    t = ISC_LIST_NEXT(t, link))
+               {
+                       isc_boolean_t ns_existed, dname_existed;
+                       isc_boolean_t ns_exists, dname_exists;
+
+                       if (oldver != NULL)
+                               CHECK(rrset_exists(db, oldver, &t->name,
+                                                 dns_rdatatype_ns, 0,
+                                                 &ns_existed));
+                       else
+                               ns_existed = ISC_FALSE;
+                       if (oldver != NULL)
+                               CHECK(rrset_exists(db, oldver, &t->name,
+                                                  dns_rdatatype_dname, 0,
+                                                  &dname_existed));
+                       else
+                               dname_existed = ISC_FALSE;
+                       CHECK(rrset_exists(db, newver, &t->name,
+                                          dns_rdatatype_ns, 0, &ns_exists));
+                       CHECK(rrset_exists(db, newver, &t->name,
+                                          dns_rdatatype_dname, 0,
+                                          &dname_exists));
+                       if ((ns_exists || dname_exists) ==
+                           (ns_existed || dname_existed))
+                               continue;
                        /*
-                        * There is a NSEC, but we don't know if it is correct.
-                        * Delete it and create a correct one to be sure.
-                        * If the update was unnecessary, the diff minimization
-                        * will take care of eliminating it from the journal,
-                        * IXFRs, etc.
-                        *
-                        * The RRSIG bit should always be set in the NSECs
-                        * we generate, because they will all get RRSIG NSECs.
-                        * (XXX what if the zone keys are missing?).
-                        * Because the RRSIG NSECs have not necessarily been
-                        * created yet, the correctness of the bit mask relies
-                        * on the assumption that NSECs are only created if
-                        * there is other data, and if there is other data,
-                        * there are other RRSIGs.
+                        * There was a delegation change.  Mark all subdomains
+                        * of t->name as potentially needing a NSEC update.
                         */
-                       CHECK(add_nsec(log, zone, db, newver, &t->name,
-                                      nsecttl, &nsec_diff));
+                       CHECK(namelist_append_subdomain(db, &t->name,
+                                                       &state->affected));
                }
-       }
+               ISC_LIST_APPENDLIST(state->affected.tuples,
+                                   state->diffnames.tuples, link);
+               INSIST(ISC_LIST_EMPTY(state->diffnames.tuples));
 
-       /*
-        * Minimize the set of NSEC updates so that we don't
-        * have to regenerate the RRSIG NSECs for NSECs that were
-        * replaced with identical ones.
-        */
-       while ((t = ISC_LIST_HEAD(nsec_diff.tuples)) != NULL) {
-               ISC_LIST_UNLINK(nsec_diff.tuples, t, link);
-               dns_diff_appendminimal(&nsec_mindiff, &t);
-       }
+               CHECK(uniqify_name_list(&state->affected));
 
-       update_log(log, zone, ISC_LOG_DEBUG(3), "signing rebuilt NSEC chain");
+               /*FALLTHROUGH*/
+       case process_nsec:
+               state->state = process_nsec;
 
-       /* Update RRSIG NSECs. */
-       for (t = ISC_LIST_HEAD(nsec_mindiff.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               if (t->op == DNS_DIFFOP_DEL) {
-                       CHECK(delete_if(true_p, db, newver, &t->name,
-                                       dns_rdatatype_rrsig, dns_rdatatype_nsec,
-                                       NULL, &sig_diff));
-               } else if (t->op == DNS_DIFFOP_ADD) {
-                       CHECK(add_sigs(log, zone, db, newver, &t->name,
-                                      dns_rdatatype_nsec, &sig_diff,
-                                      zone_keys, nkeys, inception, expire,
-                                      check_ksk, keyset_kskonly));
-               } else {
-                       INSIST(0);
+               /*
+                * Determine which names should have NSECs, and delete/create
+                * NSECs to make it so.  We don't know the final NSEC targets
+                * yet, so we just create placeholder NSECs with arbitrary
+                * contents to indicate that their respective owner names
+                * should be part of the NSEC chain.
+                */
+               while ((t = ISC_LIST_HEAD(state->affected.tuples)) != NULL) {
+                       isc_boolean_t exists;
+                       dns_name_t *name = &t->name;
+
+                       CHECK(name_exists(db, newver, name, &exists));
+                       if (! exists)
+                               goto unlink;
+                       CHECK(is_active(db, newver, name, &flag, &cut, NULL));
+                       if (!flag) {
+                               /*
+                                * This name is obscured.  Delete any
+                                * existing NSEC record.
+                                */
+                               CHECK(delete_if(true_p, db, newver, name,
+                                               dns_rdatatype_nsec, 0,
+                                               NULL, &state->nsec_diff));
+                               CHECK(delete_if(rrsig_p, db, newver, name,
+                                               dns_rdatatype_any, 0, NULL,
+                                               diff));
+                       } else {
+                               /*
+                                * This name is not obscured.  It needs to have
+                                * a NSEC unless it is the at the origin, in
+                                * which case it should already exist if there
+                                * is a complete NSEC chain and if there isn't
+                                * a complete NSEC chain we don't want to add
+                                * one as that would signal that there is a
+                                * complete NSEC chain.
+                                */
+                               if (!dns_name_equal(name, dns_db_origin(db))) {
+                                       CHECK(rrset_exists(db, newver, name,
+                                                          dns_rdatatype_nsec,
+                                                          0, &flag));
+                                       if (!flag)
+                                               CHECK(add_placeholder_nsec(db,
+                                                         newver, name, diff));
+                               }
+                               CHECK(add_exposed_sigs(log, zone, db, newver,
+                                                      name, cut,
+                                                      &state->sig_diff,
+                                                      state->zone_keys,
+                                                      state->nkeys,
+                                                      state->inception,
+                                                      state->expire,
+                                                      state->check_ksk,
+                                                      state->keyset_kskonly,
+                                                      &sigs));
+                       }
+        unlink:
+                       ISC_LIST_UNLINK(state->affected.tuples, t, link);
+                       ISC_LIST_APPEND(state->work.tuples, t, link);
+                       if (state != &mystate && sigs > maxsigs)
+                               return (DNS_R_CONTINUE);
                }
-       }
-
- update_nsec3:
+               ISC_LIST_APPENDLIST(state->affected.tuples,
+                                   state->work.tuples, link);
 
-       /* Record our changes for the journal. */
-       while ((t = ISC_LIST_HEAD(sig_diff.tuples)) != NULL) {
-               ISC_LIST_UNLINK(sig_diff.tuples, t, link);
-               dns_diff_appendminimal(diff, &t);
-       }
-       while ((t = ISC_LIST_HEAD(nsec_mindiff.tuples)) != NULL) {
-               ISC_LIST_UNLINK(nsec_mindiff.tuples, t, link);
-               dns_diff_appendminimal(diff, &t);
-       }
+               /*
+                * Now we know which names are part of the NSEC chain.
+                * Make them all point at their correct targets.
+                */
+               for (t = ISC_LIST_HEAD(state->affected.tuples);
+                    t != NULL;
+                    t = ISC_LIST_NEXT(t, link))
+               {
+                       CHECK(rrset_exists(db, newver, &t->name,
+                                          dns_rdatatype_nsec, 0, &flag));
+                       if (flag) {
+                               /*
+                                * There is a NSEC, but we don't know if it
+                                * is correct. Delete it and create a correct
+                                * one to be sure. If the update was
+                                * unnecessary, the diff minimization
+                                * will take care of eliminating it from the
+                                * journal, IXFRs, etc.
+                                *
+                                * The RRSIG bit should always be set in the
+                                * NSECs we generate, because they will all
+                                * get RRSIG NSECs.
+                                * (XXX what if the zone keys are missing?).
+                                * Because the RRSIG NSECs have not necessarily
+                                * been created yet, the correctness of the
+                                * bit mask relies on the assumption that NSECs
+                                * are only created if there is other data, and
+                                * if there is other data, there are other
+                                * RRSIGs.
+                                */
+                               CHECK(add_nsec(log, zone, db, newver, &t->name,
+                                              state->nsecttl,
+                                              &state->nsec_diff));
+                       }
+               }
 
-       INSIST(ISC_LIST_EMPTY(sig_diff.tuples));
-       INSIST(ISC_LIST_EMPTY(nsec_diff.tuples));
-       INSIST(ISC_LIST_EMPTY(nsec_mindiff.tuples));
+               /*
+                * Minimize the set of NSEC updates so that we don't
+                * have to regenerate the RRSIG NSECs for NSECs that were
+                * replaced with identical ones.
+                */
+               while ((t = ISC_LIST_HEAD(state->nsec_diff.tuples)) != NULL) {
+                       ISC_LIST_UNLINK(state->nsec_diff.tuples, t, link);
+                       dns_diff_appendminimal(&state->nsec_mindiff, &t);
+               }
 
-       if (!build_nsec3) {
                update_log(log, zone, ISC_LOG_DEBUG(3),
-                          "no NSEC3 chains to rebuild");
-               goto failure;
-       }
-
-       update_log(log, zone, ISC_LOG_DEBUG(3), "rebuilding NSEC3 chains");
-
-       dns_diff_clear(&diffnames);
-       dns_diff_clear(&affected);
+                          "signing rebuilt NSEC chain");
+
+               /*FALLTHROUGH*/
+       case sign_nsec:
+               state->state = sign_nsec;
+               /* Update RRSIG NSECs. */
+               while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+               {
+                       if (t->op == DNS_DIFFOP_DEL) {
+                               CHECK(delete_if(true_p, db, newver, &t->name,
+                                               dns_rdatatype_rrsig,
+                                               dns_rdatatype_nsec,
+                                               NULL, &state->sig_diff));
+                       } else if (t->op == DNS_DIFFOP_ADD) {
+                               CHECK(add_sigs(log, zone, db, newver, &t->name,
+                                              dns_rdatatype_nsec,
+                                              &state->sig_diff,
+                                              state->zone_keys, state->nkeys,
+                                              state->inception, state->expire,
+                                              state->check_ksk,
+                                              state->keyset_kskonly));
+                               sigs++;
+                       } else {
+                               INSIST(0);
+                       }
+                       ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+                       ISC_LIST_APPEND(state->work.tuples, t, link);
+                       if (state != &mystate && sigs > maxsigs)
+                               return (DNS_R_CONTINUE);
+               }
+               ISC_LIST_APPENDLIST(state->nsec_mindiff.tuples,
+                                   state->work.tuples, link);
+               /*FALLTHROUGH*/
+       case update_nsec3:
+               state->state = update_nsec3;
+
+               /* Record our changes for the journal. */
+               while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) {
+                       ISC_LIST_UNLINK(state->sig_diff.tuples, t, link);
+                       dns_diff_appendminimal(diff, &t);
+               }
+               while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+               {
+                       ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+                       dns_diff_appendminimal(diff, &t);
+               }
 
-       CHECK(dns_diff_sort(diff, temp_order));
+               INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples));
+               INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples));
+               INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples));
 
-       /*
-        * Find names potentially affected by delegation changes
-        * (obscured by adding an NS or DNAME, or unobscured by
-        * removing one).
-        */
-       t = ISC_LIST_HEAD(diff->tuples);
-       while (t != NULL) {
-               dns_name_t *name = &t->name;
+               if (!build_nsec3) {
+                       update_log(log, zone, ISC_LOG_DEBUG(3),
+                                  "no NSEC3 chains to rebuild");
+                       goto failure;
+               }
 
-               isc_boolean_t ns_existed, dname_existed;
-               isc_boolean_t ns_exists, dname_exists;
-               isc_boolean_t exists, existed;
+               update_log(log, zone, ISC_LOG_DEBUG(3),
+                          "rebuilding NSEC3 chains");
 
-               if (t->rdata.type == dns_rdatatype_nsec ||
-                   t->rdata.type == dns_rdatatype_rrsig) {
-                       t = ISC_LIST_NEXT(t, link);
-                       continue;
-               }
+               dns_diff_clear(&state->diffnames);
+               dns_diff_clear(&state->affected);
 
-               CHECK(namelist_append_name(&affected, name));
+               CHECK(dns_diff_sort(diff, temp_order));
 
-               if (oldver != NULL)
-                       CHECK(rrset_exists(db, oldver, name, dns_rdatatype_ns,
-                                          0, &ns_existed));
-               else
-                       ns_existed = ISC_FALSE;
-               if (oldver != NULL)
-                       CHECK(rrset_exists(db, oldver, name,
-                                          dns_rdatatype_dname, 0,
-                                          &dname_existed));
-               else
-                       dname_existed = ISC_FALSE;
-               CHECK(rrset_exists(db, newver, name, dns_rdatatype_ns, 0,
-                                  &ns_exists));
-               CHECK(rrset_exists(db, newver, name, dns_rdatatype_dname, 0,
-                                  &dname_exists));
-
-               exists = ns_exists || dname_exists;
-               existed = ns_existed || dname_existed;
-               if (exists == existed)
-                       goto nextname;
                /*
-                * There was a delegation change.  Mark all subdomains
-                * of t->name as potentially needing a NSEC3 update.
+                * Find names potentially affected by delegation changes
+                * (obscured by adding an NS or DNAME, or unobscured by
+                * removing one).
                 */
-               CHECK(namelist_append_subdomain(db, name, &affected));
+               t = ISC_LIST_HEAD(diff->tuples);
+               while (t != NULL) {
+                       dns_name_t *name = &t->name;
 
-       nextname:
-               while (t != NULL && dns_name_equal(&t->name, name))
-                       t = ISC_LIST_NEXT(t, link);
-       }
+                       isc_boolean_t ns_existed, dname_existed;
+                       isc_boolean_t ns_exists, dname_exists;
+                       isc_boolean_t exists, existed;
 
-       for (t = ISC_LIST_HEAD(affected.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link)) {
-               dns_name_t *name = &t->name;
+                       if (t->rdata.type == dns_rdatatype_nsec ||
+                           t->rdata.type == dns_rdatatype_rrsig) {
+                               t = ISC_LIST_NEXT(t, link);
+                               continue;
+                       }
 
-               unsecure = ISC_FALSE;   /* Silence compiler warning. */
-               CHECK(is_active(db, newver, name, &flag, &cut, &unsecure));
+                       CHECK(namelist_append_name(&state->affected, name));
 
-               if (!flag) {
-                       CHECK(delete_if(rrsig_p, db, newver, name,
-                                       dns_rdatatype_any, 0, NULL, diff));
-                       CHECK(dns_nsec3_delnsec3sx(db, newver, name,
-                                                  privatetype, &nsec_diff));
-               } else {
-                       CHECK(add_exposed_sigs(log, zone, db, newver, name,
-                                              cut, &sig_diff, zone_keys, nkeys,
-                                              inception, expire, check_ksk,
-                                              keyset_kskonly));
-                       CHECK(dns_nsec3_addnsec3sx(db, newver, name, nsecttl,
-                                                  unsecure, privatetype,
-                                                  &nsec_diff));
+                       if (oldver != NULL)
+                               CHECK(rrset_exists(db, oldver, name,
+                                                  dns_rdatatype_ns,
+                                                  0, &ns_existed));
+                       else
+                               ns_existed = ISC_FALSE;
+                       if (oldver != NULL)
+                               CHECK(rrset_exists(db, oldver, name,
+                                                  dns_rdatatype_dname, 0,
+                                                  &dname_existed));
+                       else
+                               dname_existed = ISC_FALSE;
+                       CHECK(rrset_exists(db, newver, name, dns_rdatatype_ns,
+                                          0, &ns_exists));
+                       CHECK(rrset_exists(db, newver, name,
+                                          dns_rdatatype_dname, 0,
+                                          &dname_exists));
+
+                       exists = ns_exists || dname_exists;
+                       existed = ns_existed || dname_existed;
+                       if (exists == existed)
+                               goto nextname;
+                       /*
+                        * There was a delegation change.  Mark all subdomains
+                        * of t->name as potentially needing a NSEC3 update.
+                        */
+                       CHECK(namelist_append_subdomain(db, name,
+                                                       &state->affected));
+
+       nextname:
+                       while (t != NULL && dns_name_equal(&t->name, name))
+                               t = ISC_LIST_NEXT(t, link);
                }
-       }
 
-       /*
-        * Minimize the set of NSEC3 updates so that we don't
-        * have to regenerate the RRSIG NSEC3s for NSEC3s that were
-        * replaced with identical ones.
-        */
-       while ((t = ISC_LIST_HEAD(nsec_diff.tuples)) != NULL) {
-               ISC_LIST_UNLINK(nsec_diff.tuples, t, link);
-               dns_diff_appendminimal(&nsec_mindiff, &t);
-       }
+               /*FALLTHROUGH*/
+       case process_nsec3:
+               state->state = process_nsec3;
+               while ((t = ISC_LIST_HEAD(state->affected.tuples)) != NULL) {
+                       dns_name_t *name = &t->name;
+
+                       unsecure = ISC_FALSE;   /* Silence compiler warning. */
+                       CHECK(is_active(db, newver, name, &flag, &cut,
+                                       &unsecure));
+
+                       if (!flag) {
+                               CHECK(delete_if(rrsig_p, db, newver, name,
+                                               dns_rdatatype_any, 0, NULL,
+                                               diff));
+                               CHECK(dns_nsec3_delnsec3sx(db, newver, name,
+                                                          privatetype,
+                                                          &state->nsec_diff));
+                       } else {
+                               CHECK(add_exposed_sigs(log, zone, db, newver,
+                                                      name, cut,
+                                                      &state->sig_diff,
+                                                      state->zone_keys,
+                                                      state->nkeys,
+                                                      state->inception,
+                                                      state->expire,
+                                                      state->check_ksk,
+                                                      state->keyset_kskonly,
+                                                      &sigs));
+                               CHECK(dns_nsec3_addnsec3sx(db, newver, name,
+                                                          state->nsecttl,
+                                                          unsecure,
+                                                          privatetype,
+                                                          &state->nsec_diff));
+                       }
+                       ISC_LIST_UNLINK(state->affected.tuples, t, link);
+                       ISC_LIST_APPEND(state->work.tuples, t, link);
+                       if (state != &mystate && sigs > maxsigs)
+                               return (DNS_R_CONTINUE);
+               }
+               ISC_LIST_APPENDLIST(state->affected.tuples,
+                                   state->work.tuples, link);
 
-       update_log(log, zone, ISC_LOG_DEBUG(3),
-                  "signing rebuilt NSEC3 chain");
+               /*
+                * Minimize the set of NSEC3 updates so that we don't
+                * have to regenerate the RRSIG NSEC3s for NSEC3s that were
+                * replaced with identical ones.
+                */
+               while ((t = ISC_LIST_HEAD(state->nsec_diff.tuples)) != NULL) {
+                       ISC_LIST_UNLINK(state->nsec_diff.tuples, t, link);
+                       dns_diff_appendminimal(&state->nsec_mindiff, &t);
+               }
 
-       /* Update RRSIG NSEC3s. */
-       for (t = ISC_LIST_HEAD(nsec_mindiff.tuples);
-            t != NULL;
-            t = ISC_LIST_NEXT(t, link))
-       {
-               if (t->op == DNS_DIFFOP_DEL) {
-                       CHECK(delete_if(true_p, db, newver, &t->name,
-                                       dns_rdatatype_rrsig,
-                                       dns_rdatatype_nsec3,
-                                       NULL, &sig_diff));
-               } else if (t->op == DNS_DIFFOP_ADD) {
-                       CHECK(add_sigs(log, zone, db, newver, &t->name,
-                                      dns_rdatatype_nsec3,
-                                      &sig_diff, zone_keys, nkeys,
-                                      inception, expire, check_ksk,
-                                      keyset_kskonly));
-               } else {
-                       INSIST(0);
+               update_log(log, zone, ISC_LOG_DEBUG(3),
+                          "signing rebuilt NSEC3 chain");
+
+               /*FALLTHROUGH*/
+       case sign_nsec3:
+               state->state = sign_nsec3;
+               /* Update RRSIG NSEC3s. */
+               while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+               {
+                       if (t->op == DNS_DIFFOP_DEL) {
+                               CHECK(delete_if(true_p, db, newver, &t->name,
+                                               dns_rdatatype_rrsig,
+                                               dns_rdatatype_nsec3,
+                                               NULL, &state->sig_diff));
+                       } else if (t->op == DNS_DIFFOP_ADD) {
+                               CHECK(add_sigs(log, zone, db, newver, &t->name,
+                                              dns_rdatatype_nsec3,
+                                              &state->sig_diff,
+                                              state->zone_keys,
+                                              state->nkeys, state->inception,
+                                              state->expire, state->check_ksk,
+                                              state->keyset_kskonly));
+                               sigs++;
+                       } else {
+                               INSIST(0);
+                       }
+                       ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+                       ISC_LIST_APPEND(state->work.tuples, t, link);
+                       if (state != &mystate && sigs > maxsigs)
+                               return (DNS_R_CONTINUE);
                }
-       }
+               ISC_LIST_APPENDLIST(state->nsec_mindiff.tuples,
+                                   state->work.tuples, link);
 
-       /* Record our changes for the journal. */
-       while ((t = ISC_LIST_HEAD(sig_diff.tuples)) != NULL) {
-               ISC_LIST_UNLINK(sig_diff.tuples, t, link);
-               dns_diff_appendminimal(diff, &t);
-       }
-       while ((t = ISC_LIST_HEAD(nsec_mindiff.tuples)) != NULL) {
-               ISC_LIST_UNLINK(nsec_mindiff.tuples, t, link);
-               dns_diff_appendminimal(diff, &t);
-       }
+               /* Record our changes for the journal. */
+               while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) {
+                       ISC_LIST_UNLINK(state->sig_diff.tuples, t, link);
+                       dns_diff_appendminimal(diff, &t);
+               }
+               while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL)
+               {
+                       ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link);
+                       dns_diff_appendminimal(diff, &t);
+               }
 
-       INSIST(ISC_LIST_EMPTY(sig_diff.tuples));
-       INSIST(ISC_LIST_EMPTY(nsec_diff.tuples));
-       INSIST(ISC_LIST_EMPTY(nsec_mindiff.tuples));
+               INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples));
+               INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples));
+               INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples));
+               break;
+       default:
+               INSIST(0);
+       }
 
  failure:
-       dns_diff_clear(&sig_diff);
-       dns_diff_clear(&nsec_diff);
-       dns_diff_clear(&nsec_mindiff);
+       dns_diff_clear(&state->sig_diff);
+       dns_diff_clear(&state->nsec_diff);
+       dns_diff_clear(&state->nsec_mindiff);
+
+       dns_diff_clear(&state->affected);
+       dns_diff_clear(&state->diffnames);
+       dns_diff_clear(&state->work);
 
-       dns_diff_clear(&affected);
-       dns_diff_clear(&diffnames);
+       for (i = 0; i < state->nkeys; i++)
+               dst_key_free(&state->zone_keys[i]);
 
-       for (i = 0; i < nkeys; i++)
-               dst_key_free(&zone_keys[i]);
+       if (state != &mystate && state != NULL) {
+               *statep = NULL;
+               state->magic = 0;
+               isc_mem_put(diff->mctx, state, sizeof(*state));
+       }
 
        return (result);
 }
index 8afa82586cde2e28f1eaf889775e0396af780223..57ced0c541f9131875855ebade475ea64d4eac0c 100644 (file)
@@ -411,6 +411,17 @@ struct dns_zone {
         */
        dns_ttl_t               maxttl;
 
+       /*
+        * Inline zone signing state.
+        */
+       dns_diff_t              rss_diff;
+       isc_eventlist_t         rss_events;
+       dns_dbversion_t         *rss_newver;
+       dns_dbversion_t         *rss_oldver;
+       dns_db_t                *rss_db;
+       dns_zone_t              *rss_raw;
+       isc_event_t             *rss_event;
+       dns_update_state_t      *rss_state;
 };
 
 typedef struct {
@@ -1031,6 +1042,13 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) {
        zone->sourceserialset = ISC_FALSE;
        zone->requestixfr = ISC_TRUE;
        zone->requestexpire = ISC_TRUE;
+       ISC_LIST_INIT(zone->rss_events);
+       zone->rss_db = NULL;
+       zone->rss_raw = NULL;
+       zone->rss_newver = NULL;
+       zone->rss_oldver = NULL;
+       zone->rss_event = NULL;
+       zone->rss_state = NULL;
 
        zone->magic = ZONE_MAGIC;
 
@@ -9483,7 +9501,8 @@ zone_maintenance(dns_zone_t *zone) {
                break;
        case dns_zone_master:
                if (!isc_time_isepoch(&zone->refreshkeytime) &&
-                   isc_time_compare(&now, &zone->refreshkeytime) >= 0)
+                   isc_time_compare(&now, &zone->refreshkeytime) >= 0 &&
+                   zone->rss_event == NULL)
                        zone_rekey(zone);
        default:
                break;
@@ -9496,6 +9515,8 @@ zone_maintenance(dns_zone_t *zone) {
                /*
                 * Do we need to sign/resign some RRsets?
                 */
+               if (zone->rss_event != NULL)
+                       break;
                if (!isc_time_isepoch(&zone->signingtime) &&
                    isc_time_compare(&now, &zone->signingtime) >= 0)
                        zone_sign(zone);
@@ -13564,61 +13585,74 @@ sync_secure_db(dns_zone_t *seczone, dns_zone_t *raw, dns_db_t *secdb,
 static void
 receive_secure_serial(isc_task_t *task, isc_event_t *event) {
        static char me[] = "receive_secure_serial";
-       isc_result_t result;
+       isc_result_t result = ISC_R_SUCCESS;
        dns_journal_t *rjournal = NULL;
+       dns_journal_t *sjournal = NULL;
        isc_uint32_t start, end;
-       dns_zone_t *zone, *raw = NULL;
-       dns_db_t *db = NULL;
-       dns_dbversion_t *newver = NULL, *oldver = NULL;
-       dns_diff_t diff;
+       dns_zone_t *zone;
        dns_difftuple_t *tuple = NULL, *soatuple = NULL;
        dns_update_log_t log = { update_log_cb, NULL };
        isc_time_t timenow;
 
+       UNUSED(task);
+
        zone = event->ev_arg;
        end = ((struct secure_event *)event)->serial;
-       isc_event_free(&event);
 
        ENTER;
 
        LOCK_ZONE(zone);
 
-       dns_diff_init(zone->mctx, &diff);
+       /*
+        * If we are already processing a receive secure serial event
+        * for the zone, just queue the new one and exit.
+        */
+       if (zone->rss_event != NULL && zone->rss_event != event) {
+               ISC_LIST_APPEND(zone->rss_events, event, ev_link);
+               UNLOCK_ZONE(zone);
+               return;
+       }
 
-       UNUSED(task);
+ nextevent:
+       if (zone->rss_event != NULL) {
+               INSIST(zone->rss_event == event);
+               UNLOCK_ZONE(zone);
+       } else {
+               zone->rss_event = event;
+               dns_diff_init(zone->mctx, &zone->rss_diff);
 
-       ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
-       if (zone->db != NULL)
-               dns_db_attach(zone->db, &db);
-       ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
+               /*
+                * zone->db may be NULL, if the load from disk failed.
+                */
+               result = ISC_R_SUCCESS;
+               ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
+               if (zone->db != NULL)
+                       dns_db_attach(zone->db, &zone->rss_db);
+               else
+                       result = ISC_R_FAILURE;
+               ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
 
-       if (zone->raw != NULL)
-               dns_zone_attach(zone->raw, &raw);
-       UNLOCK_ZONE(zone);
+               if (result == ISC_R_SUCCESS && zone->raw != NULL)
+                       dns_zone_attach(zone->raw, &zone->rss_raw);
+               else
+                       result = ISC_R_FAILURE;
 
-       /*
-        * zone->db may be NULL if the load from disk failed.
-        */
-       if (db == NULL || raw == NULL) {
-               result = ISC_R_FAILURE;
-               goto failure;
-       }
+               UNLOCK_ZONE(zone);
 
-       /*
-        * We first attempt to sync the raw zone to the secure zone
-        * by using the raw zone's journal, applying all the deltas
-        * from the latest source-serial of the secure zone up to
-        * the current serial number of the raw zone.
-        *
-        * If that fails, then we'll fall back to a direct comparison
-        * between raw and secure zones.
-        */
-       result = dns_journal_open(raw->mctx, raw->journal,
-                                 DNS_JOURNAL_WRITE, &rjournal);
-       if (result != ISC_R_SUCCESS)
-               goto failure;
-       else {
-               dns_journal_t *sjournal = NULL;
+               CHECK(result);
+
+               /*
+                * We first attempt to sync the raw zone to the secure zone
+                * by using the raw zone's journal, applying all the deltas
+                * from the latest source-serial of the secure zone up to
+                * the current serial number of the raw zone.
+                *
+                * If that fails, then we'll fall back to a direct comparison
+                * between raw and secure zones.
+                */
+               CHECK(dns_journal_open(zone->rss_raw->mctx,
+                                      zone->rss_raw->journal,
+                                      DNS_JOURNAL_WRITE, &rjournal));
 
                result = dns_journal_open(zone->mctx, zone->journal,
                                          DNS_JOURNAL_READ, &sjournal);
@@ -13632,9 +13666,9 @@ receive_secure_serial(isc_task_t *task, isc_event_t *event) {
                if (sjournal != NULL) {
                        isc_uint32_t serial;
                        /*
-                        * We read the secure journal first, if that exists
-                        * use its value provided it is greater that from the
-                        * raw journal.
+                        * We read the secure journal first, if that
+                        * exists use its value provided it is greater
+                        * that from the raw journal.
                         */
                        if (dns_journal_get_sourceserial(sjournal, &serial)) {
                                if (isc_serial_gt(serial, start))
@@ -13642,50 +13676,78 @@ receive_secure_serial(isc_task_t *task, isc_event_t *event) {
                        }
                        dns_journal_destroy(&sjournal);
                }
-       }
 
-       dns_db_currentversion(db, &oldver);
-       CHECK(dns_db_newversion(db, &newver));
+               dns_db_currentversion(zone->rss_db, &zone->rss_oldver);
+               CHECK(dns_db_newversion(zone->rss_db, &zone->rss_newver));
 
-       /*
-        * Try to apply diffs from the raw zone's journal to the secure
-        * zone.  If that fails, we recover by syncing up the databases
-        * directly.
-        */
-       result = sync_secure_journal(zone, raw, rjournal, start, end,
-                                    &soatuple, &diff);
-       if (result == DNS_R_UNCHANGED)
-               goto failure;
-       else if (result != ISC_R_SUCCESS)
-               CHECK(sync_secure_db(zone, raw, db, oldver, &soatuple, &diff));
-
-       CHECK(dns_diff_apply(&diff, db, newver));
-
-       if (soatuple != NULL) {
-               isc_uint32_t oldserial, newserial, desired;
-
-               CHECK(dns_db_createsoatuple(db, oldver, diff.mctx,
-                                           DNS_DIFFOP_DEL, &tuple));
-               oldserial = dns_soa_getserial(&tuple->rdata);
-               newserial = desired = dns_soa_getserial(&soatuple->rdata);
-               if (!isc_serial_gt(newserial, oldserial)) {
-                       newserial = oldserial + 1;
-                       if (newserial == 0)
-                               newserial++;
-                       dns_soa_setserial(newserial, &soatuple->rdata);
-               }
-               CHECK(do_one_tuple(&tuple, db, newver, &diff));
-               CHECK(do_one_tuple(&soatuple, db, newver, &diff));
-               dns_zone_log(zone, ISC_LOG_INFO, "serial %u (unsigned %u)",
-                            newserial, desired);
-       } else
-               CHECK(update_soa_serial(db, newver, &diff, zone->mctx,
-                                       zone->updatemethod));
+               /*
+                * Try to apply diffs from the raw zone's journal to the secure
+                * zone.  If that fails, we recover by syncing up the databases
+                * directly.
+                */
+               result = sync_secure_journal(zone, zone->rss_raw, rjournal,
+                                            start, end, &soatuple,
+                                            &zone->rss_diff);
+               if (result == DNS_R_UNCHANGED)
+                       goto failure;
+               else if (result != ISC_R_SUCCESS)
+                       CHECK(sync_secure_db(zone, zone->rss_raw, zone->rss_db,
+                                            zone->rss_oldver, &soatuple,
+                                            &zone->rss_diff));
+
+               CHECK(dns_diff_apply(&zone->rss_diff, zone->rss_db,
+                                    zone->rss_newver));
+
+               if (soatuple != NULL) {
+                       isc_uint32_t oldserial, newserial, desired;
+
+                       CHECK(dns_db_createsoatuple(zone->rss_db,
+                                                   zone->rss_oldver,
+                                                   zone->rss_diff.mctx,
+                                                   DNS_DIFFOP_DEL, &tuple));
+                       oldserial = dns_soa_getserial(&tuple->rdata);
+                       newserial = desired =
+                                   dns_soa_getserial(&soatuple->rdata);
+                       if (!isc_serial_gt(newserial, oldserial)) {
+                               newserial = oldserial + 1;
+                               if (newserial == 0)
+                                       newserial++;
+                               dns_soa_setserial(newserial, &soatuple->rdata);
+                       }
+                       CHECK(do_one_tuple(&tuple, zone->rss_db,
+                                          zone->rss_newver, &zone->rss_diff));
+                       CHECK(do_one_tuple(&soatuple, zone->rss_db,
+                                          zone->rss_newver, &zone->rss_diff));
+                       dns_zone_log(zone, ISC_LOG_INFO,
+                                    "serial %u (unsigned %u)",
+                                    newserial, desired);
+               } else
+                       CHECK(update_soa_serial(zone->rss_db, zone->rss_newver,
+                                               &zone->rss_diff, zone->mctx,
+                                               zone->updatemethod));
 
-       CHECK(dns_update_signatures(&log, zone, db, oldver, newver,
-                                   &diff, zone->sigvalidityinterval));
+       }
+       result = dns_update_signaturesinc(&log, zone, zone->rss_db,
+                                         zone->rss_oldver, zone->rss_newver,
+                                         &zone->rss_diff,
+                                         zone->sigvalidityinterval,
+                                         &zone->rss_state);
+       if (result == DNS_R_CONTINUE) {
+               if (rjournal != NULL)
+                       dns_journal_destroy(&rjournal);
+               isc_task_send(task, &event);
+               fprintf(stderr, "looping on dns_update_signaturesinc\n");
+               return;
+       }
+       if (result != ISC_R_SUCCESS)
+               goto failure;
 
-       CHECK(zone_journal(zone, &diff, &end, "receive_secure_serial"));
+       if (rjournal == NULL)
+               CHECK(dns_journal_open(zone->rss_raw->mctx,
+                                      zone->rss_raw->journal,
+                                      DNS_JOURNAL_WRITE, &rjournal));
+       CHECK(zone_journal(zone, &zone->rss_diff, &end,
+                          "receive_secure_serial"));
 
        dns_journal_set_sourceserial(rjournal, end);
        dns_journal_commit(rjournal);
@@ -13701,12 +13763,15 @@ receive_secure_serial(isc_task_t *task, isc_event_t *event) {
        zone_settimer(zone, &timenow);
        UNLOCK_ZONE(zone);
 
-       dns_db_closeversion(db, &oldver, ISC_FALSE);
-       dns_db_closeversion(db, &newver, ISC_TRUE);
+       dns_db_closeversion(zone->rss_db, &zone->rss_oldver, ISC_FALSE);
+       dns_db_closeversion(zone->rss_db, &zone->rss_newver, ISC_TRUE);
 
  failure:
-       if (raw != NULL)
-               dns_zone_detach(&raw);
+       isc_event_free(&zone->rss_event);
+       event = ISC_LIST_HEAD(zone->rss_events);
+
+       if (zone->rss_raw != NULL)
+               dns_zone_detach(&zone->rss_raw);
        if (result != ISC_R_SUCCESS)
                dns_zone_log(zone, ISC_LOG_ERROR, "receive_secure_serial: %s",
                             dns_result_totext(result));
@@ -13714,20 +13779,28 @@ receive_secure_serial(isc_task_t *task, isc_event_t *event) {
                dns_difftuple_free(&tuple);
        if (soatuple != NULL)
                dns_difftuple_free(&soatuple);
-       if (db != NULL) {
-               if (oldver != NULL)
-                       dns_db_closeversion(db, &oldver, ISC_FALSE);
-               if (newver != NULL)
-                       dns_db_closeversion(db, &newver, ISC_FALSE);
-               dns_db_detach(&db);
+       if (zone->rss_db != NULL) {
+               if (zone->rss_oldver != NULL)
+                       dns_db_closeversion(zone->rss_db, &zone->rss_oldver,
+                                           ISC_FALSE);
+               if (zone->rss_newver != NULL)
+                       dns_db_closeversion(zone->rss_db, &zone->rss_newver,
+                                           ISC_FALSE);
+               dns_db_detach(&zone->rss_db);
        }
+       INSIST(zone->rss_oldver == NULL);
+       INSIST(zone->rss_newver == NULL);
        if (rjournal != NULL)
                dns_journal_destroy(&rjournal);
-       dns_diff_clear(&diff);
-       dns_zone_idetach(&zone);
+       dns_diff_clear(&zone->rss_diff);
 
-       INSIST(oldver == NULL);
-       INSIST(newver == NULL);
+       if (event != NULL) {
+               LOCK_ZONE(zone);
+               INSIST(zone->irefs > 1);
+               zone->irefs--;
+               goto nextevent;
+       }
+       dns_zone_idetach(&zone);
 }
 
 static isc_result_t
@@ -16720,6 +16793,12 @@ dns_zone_setsignatures(dns_zone_t *zone, isc_uint32_t signatures) {
        zone->signatures = signatures;
 }
 
+isc_uint32_t
+dns_zone_getsignatures(dns_zone_t *zone) {
+       REQUIRE(DNS_ZONE_VALID(zone));
+       return (zone->signatures);
+}
+
 void
 dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type) {
        REQUIRE(DNS_ZONE_VALID(zone));