diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1521d11760..0ecd574e41 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2236,6 +2236,7 @@ respdiff-third-party: .respdiff-recent-named: &respdiff_recent_named <<: *respdiff_job <<: *base_image + allow_failure: true #GL!11355 needs: - job: ci-variables artifacts: true diff --git a/lib/dns/db.c b/lib/dns/db.c index 19417acbfa..eb771bee41 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -315,6 +315,57 @@ dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { return ISC_R_NOTIMPLEMENTED; } +isc_result_t +dns_db_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks) { + /* + * Begin updating 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + if (db->methods->beginupdate != NULL) { + return (db->methods->beginupdate)(db, ver, callbacks); + } + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t +dns_db_commitupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + /* + * Commit the update to 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + if (db->methods->commitupdate != NULL) { + return (db->methods->commitupdate)(db, callbacks); + } + + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t +dns_db_abortupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + /* + * Abort the update to 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + if (db->methods->abortupdate != NULL) { + return (db->methods->abortupdate)(db, callbacks); + } + + return ISC_R_NOTIMPLEMENTED; +} + isc_result_t dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format, unsigned int options) { diff --git a/lib/dns/diff.c b/lib/dns/diff.c index d3885c7311..0262211c28 100644 --- a/lib/dns/diff.c +++ b/lib/dns/diff.c @@ -211,42 +211,6 @@ dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuplep) { } } -static isc_stdtime_t -setresign(dns_rdataset_t *modified) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_rrsig_t sig; - int64_t when; - isc_result_t result; - - result = dns_rdataset_first(modified); - INSIST(result == ISC_R_SUCCESS); - dns_rdataset_current(modified, &rdata); - (void)dns_rdata_tostruct(&rdata, &sig, NULL); - if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { - when = 0; - } else { - when = dns_time64_from32(sig.timeexpire); - } - dns_rdata_reset(&rdata); - - result = dns_rdataset_next(modified); - while (result == ISC_R_SUCCESS) { - dns_rdataset_current(modified, &rdata); - (void)dns_rdata_tostruct(&rdata, &sig, NULL); - if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { - goto next_rr; - } - if (when == 0 || dns_time64_from32(sig.timeexpire) < when) { - when = dns_time64_from32(sig.timeexpire); - } - next_rr: - dns_rdata_reset(&rdata); - result = dns_rdataset_next(modified); - } - INSIST(result == ISC_R_NOMORE); - return (isc_stdtime_t)when; -} - static void getownercase(dns_rdataset_t *rdataset, dns_name_t *name) { if (dns_rdataset_isassociated(rdataset)) { @@ -261,6 +225,91 @@ setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) { } } +static isc_result_t +update_rdataset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_rdataset_t *rds, dns_diffop_t op) { + isc_result_t result; + unsigned int options; + dns_rdataset_t ardataset; + dns_dbnode_t *node = NULL; + bool is_resign; + + dns_rdataset_init(&ardataset); + + is_resign = rds->type == dns_rdatatype_rrsig && + (op == DNS_DIFFOP_DELRESIGN || op == DNS_DIFFOP_ADDRESIGN); + + if (rds->type != dns_rdatatype_nsec3 && + rds->covers != dns_rdatatype_nsec3) + { + CHECK(dns_db_findnode(db, name, true, &node)); + } else { + CHECK(dns_db_findnsec3node(db, name, true, &node)); + } + + switch (op) { + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | + DNS_DBADD_EXACTTTL; + CHECK(dns_db_addrdataset(db, node, ver, 0, rds, options, + &ardataset)); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UNCHANGED: + case DNS_R_NXRRSET: + setownercase(&ardataset, name); + CHECK(result); + break; + default: + CHECK(result); + break; + } + break; + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; + result = dns_db_subtractrdataset(db, node, ver, rds, options, + &ardataset); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UNCHANGED: + case DNS_R_NXRRSET: + getownercase(&ardataset, name); + CHECK(result); + break; + default: + CHECK(result); + break; + } + break; + default: + UNREACHABLE(); + } + + if (is_resign) { + isc_stdtime_t resign; + resign = dns_rdataset_minresign(&ardataset); + dns_db_setsigningtime(db, &ardataset, resign); + } + +cleanup: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + return result; +} + +isc_result_t +update_callback(void *arg, const dns_name_t *name, dns_rdataset_t *rds, + dns_diffop_t op DNS__DB_FLARG) { + dns_updatectx_t *ctx = arg; + return update_rdataset(ctx->db, ctx->ver, (dns_name_t *)name, rds, op); +} + static const char * optotext(dns_diffop_t op) { switch (op) { @@ -278,23 +327,24 @@ optotext(dns_diffop_t op) { } static isc_result_t -diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, - bool warn) { +diff_apply(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks) { dns_difftuple_t *t; - dns_dbnode_t *node = NULL; isc_result_t result; char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; + dns_updatectx_t *ctx; REQUIRE(DNS_DIFF_VALID(diff)); - REQUIRE(DNS_DB_VALID(db)); + REQUIRE(callbacks != NULL); + REQUIRE(callbacks->update != NULL); + + ctx = callbacks->add_private; t = ISC_LIST_HEAD(diff->tuples); while (t != NULL) { dns_name_t *name; - INSIST(node == NULL); name = &t->name; /* * Find the node. @@ -311,8 +361,6 @@ diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, dns_diffop_t op; dns_rdatalist_t rdl; dns_rdataset_t rds; - dns_rdataset_t ardataset; - unsigned int options; op = t->op; type = t->rdata.type; @@ -340,16 +388,6 @@ diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, rdl.rdclass = t->rdata.rdclass; rdl.ttl = t->ttl; - node = NULL; - if (type != dns_rdatatype_nsec3 && - covers != dns_rdatatype_nsec3) - { - CHECK(dns_db_findnode(db, name, true, &node)); - } else { - CHECK(dns_db_findnsec3node(db, name, true, - &node)); - } - while (t != NULL && dns_name_equal(&t->name, name) && t->op == op && t->rdata.type == type && rdata_covers(&t->rdata) == covers) @@ -359,7 +397,7 @@ diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, * dns_rdataset_setownercase. */ name = &t->name; - if (t->ttl != rdl.ttl && warn) { + if (t->ttl != rdl.ttl && ctx->warn) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(t->rdata.type, @@ -387,54 +425,22 @@ diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, * Convert the rdatalist into a rdataset. */ dns_rdataset_init(&rds); - dns_rdataset_init(&ardataset); dns_rdatalist_tordataset(&rdl, &rds); rds.trust = dns_trust_ultimate; /* * Merge the rdataset into the database. */ - switch (op) { - case DNS_DIFFOP_ADD: - case DNS_DIFFOP_ADDRESIGN: - options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | - DNS_DBADD_EXACTTTL; - result = dns_db_addrdataset(db, node, ver, 0, - &rds, options, - &ardataset); - break; - case DNS_DIFFOP_DEL: - case DNS_DIFFOP_DELRESIGN: - options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; - result = dns_db_subtractrdataset(db, node, ver, - &rds, options, - &ardataset); - break; - default: - UNREACHABLE(); - } + result = callbacks->update(callbacks->add_private, name, + &rds, op DNS__DB_FILELINE); - if (result == ISC_R_SUCCESS) { - if (rds.type == dns_rdatatype_rrsig && - (op == DNS_DIFFOP_DELRESIGN || - op == DNS_DIFFOP_ADDRESIGN)) - { - isc_stdtime_t resign; - resign = setresign(&ardataset); - dns_db_setsigningtime(db, &ardataset, - resign); - } - if (op == DNS_DIFFOP_ADD || - op == DNS_DIFFOP_ADDRESIGN) - { - setownercase(&ardataset, name); - } - if (op == DNS_DIFFOP_DEL || - op == DNS_DIFFOP_DELRESIGN) - { - getownercase(&ardataset, name); - } - } else if (result == DNS_R_UNCHANGED) { + switch (result) { + case ISC_R_SUCCESS: + /* + * OK. + */ + break; + case DNS_R_UNCHANGED: /* * This will not happen when executing a * dynamic update, because that code will @@ -443,87 +449,83 @@ diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, * from a server that is not as careful. * Issue a warning and continue. */ - if (warn) { - dns_name_format(dns_db_origin(db), + if (ctx->warn) { + dns_name_format(dns_db_origin(ctx->db), namebuf, sizeof(namebuf)); - dns_rdataclass_format(dns_db_class(db), - classbuf, - sizeof(classbuf)); + dns_rdataclass_format( + dns_db_class(ctx->db), classbuf, + sizeof(classbuf)); isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_WARNING, "%s/%s: dns_diff_apply: " "update with no effect", namebuf, classbuf); } - if (op == DNS_DIFFOP_ADD || - op == DNS_DIFFOP_ADDRESIGN) - { - setownercase(&ardataset, name); - } - if (op == DNS_DIFFOP_DEL || - op == DNS_DIFFOP_DELRESIGN) - { - getownercase(&ardataset, name); - } - } else if (result == DNS_R_NXRRSET) { + result = ISC_R_SUCCESS; + break; + case DNS_R_NXRRSET: /* * OK. */ - if (op == DNS_DIFFOP_DEL || - op == DNS_DIFFOP_DELRESIGN) - { - getownercase(&ardataset, name); - } - if (dns_rdataset_isassociated(&ardataset)) { - dns_rdataset_disassociate(&ardataset); - } - } else { - if (result == DNS_R_NOTEXACT) { - dns_name_format(name, namebuf, - sizeof(namebuf)); - dns_rdatatype_format(type, typebuf, - sizeof(typebuf)); - dns_rdataclass_format(rdclass, classbuf, - sizeof(classbuf)); - isc_log_write( - DIFF_COMMON_LOGARGS, - ISC_LOG_ERROR, - "dns_diff_apply: %s/%s/%s: %s " - "%s", - namebuf, typebuf, classbuf, - optotext(op), - isc_result_totext(result)); - } - if (dns_rdataset_isassociated(&ardataset)) { - dns_rdataset_disassociate(&ardataset); - } - CHECK(result); - } - dns_db_detachnode(db, &node); - if (dns_rdataset_isassociated(&ardataset)) { - dns_rdataset_disassociate(&ardataset); + result = ISC_R_SUCCESS; + break; + case DNS_R_NOTEXACT: + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(type, typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(DIFF_COMMON_LOGARGS, + ISC_LOG_ERROR, + "dns_diff_apply: %s/%s/%s: %s " + "%s", + namebuf, typebuf, classbuf, + optotext(op), + isc_result_totext(result)); + break; + default: + break; } + + CHECK(result); } } return ISC_R_SUCCESS; cleanup: - if (node != NULL) { - dns_db_detachnode(db, &node); - } return result; } isc_result_t dns_diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) { - return diff_apply(diff, db, ver, true); + dns_updatectx_t ctx = { .db = db, .ver = ver, .warn = true }; + dns_rdatacallbacks_t callbacks; + dns_rdatacallbacks_init(&callbacks); + callbacks.update = update_callback; + callbacks.add_private = &ctx; + return diff_apply(diff, &callbacks); } isc_result_t dns_diff_applysilently(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) { - return diff_apply(diff, db, ver, false); + dns_updatectx_t ctx = { .db = db, .ver = ver, .warn = false }; + dns_rdatacallbacks_t callbacks; + dns_rdatacallbacks_init(&callbacks); + callbacks.update = update_callback; + callbacks.add_private = &ctx; + return diff_apply(diff, &callbacks); +} + +isc_result_t +dns_diff_apply_with_callbacks(const dns_diff_t *diff, + dns_rdatacallbacks_t *callbacks) { + REQUIRE(DNS_DIFF_VALID(diff)); + REQUIRE(callbacks != NULL); + REQUIRE(callbacks->update != NULL); + + return diff_apply(diff, callbacks); } /* XXX this duplicates lots of code in diff_apply(). */ @@ -577,8 +579,9 @@ dns_diff_load(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks) { rds.trust = dns_trust_ultimate; INSIST(op == DNS_DIFFOP_ADD); - result = callbacks->add(callbacks->add_private, name, - &rds DNS__DB_FILELINE); + result = callbacks->update( + callbacks->add_private, name, &rds, + DNS_DIFFOP_ADD DNS__DB_FILELINE); if (result == DNS_R_UNCHANGED) { isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_WARNING, diff --git a/lib/dns/include/dns/callbacks.h b/lib/dns/include/dns/callbacks.h index da15ec9255..ffe2d9ea6b 100644 --- a/lib/dns/include/dns/callbacks.h +++ b/lib/dns/include/dns/callbacks.h @@ -37,18 +37,18 @@ struct dns_rdatacallbacks { unsigned int magic; /*% - * dns_load_master calls 'add' when it has an rdataset to add - * to the database. If defined, it calls 'setup' before and - * 'commit' after adding rdatasets. + * dns_load_master calls 'update' when it has an rdataset to update + * in the database. If defined, it calls 'setup' before and + * 'commit' after updating rdatasets. * * Some database implementations will commit each rdataset as - * soon as it's added, in which case 'setup' and 'commit' need + * soon as it's updated, in which case 'setup' and 'commit' need * not be defined. However, other implementations can be * optimized by grouping rdatasets into a transaction; the * setup and commit functions allow this transaction to be * opened and committed. */ - dns_addrdatasetfunc_t add; + dns_addrdatasetfunc_t update; dns_transactionfunc_t setup; dns_transactionfunc_t commit; diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index 081c8ae834..f2af1756c5 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -83,6 +83,12 @@ typedef struct dns_dbmethods { isc_result_t (*beginload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks); isc_result_t (*endload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks); + isc_result_t (*beginupdate)(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks); + isc_result_t (*commitupdate)(dns_db_t *db, + dns_rdatacallbacks_t *callbacks); + isc_result_t (*abortupdate)(dns_db_t *db, + dns_rdatacallbacks_t *callbacks); void (*currentversion)(dns_db_t *db, dns_dbversion_t **versionp); isc_result_t (*newversion)(dns_db_t *db, dns_dbversion_t **versionp); void (*attachversion)(dns_db_t *db, dns_dbversion_t *source, @@ -537,6 +543,91 @@ dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks); * implementation used, syntax errors in the master file, etc. */ +isc_result_t +dns_db_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks); +/*%< + * Begin updating 'db'. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'callbacks' is a pointer to an initialized dns_rdatacallbacks_t + * structure. + * + * Ensures: + * + * \li On success, callbacks->add will be a valid dns_addrdatasetfunc_t + * suitable for updating records in 'db' from IXFR operations. + * callbacks->add_private will be a valid DB update context + * which should be used as 'arg' when callbacks->add is called. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_commitupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks); +/*%< + * Commit the update to 'db'. Must be safe to double-call or call after + * dns_db_abortupdate. + * + * Requires: + * + * \li 'db' is a valid database that is being updated. + * + * \li 'callbacks' is a valid dns_rdatacallbacks_t structure. + * + * \li callbacks->add_private is not NULL and is a valid database update + * context. + * + * Ensures: + * + * \li 'callbacks' is returned to its state prior to calling + * dns_db_beginupdate() + * + * Returns: + * + * \li #ISC_R_SUCCESS + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_abortupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks); +/*%< + * Abort the update to 'db'. Must be safe to double-call or call after + * dns_db_commitupdate. Must also be safe to call without having called + * dns_db_beginupdate first. + * + * Requires: + * + * \li 'db' is a valid database that is being updated. + * + * \li 'callbacks' is a valid dns_rdatacallbacks_t structure. + * + * \li callbacks->add_private is not NULL and is a valid database update + * context. + * + * Ensures: + * + * \li 'callbacks' is returned to its state prior to calling + * dns_db_beginupdate() + * + * Returns: + * + * \li #ISC_R_SUCCESS + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + isc_result_t dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format, unsigned int options); diff --git a/lib/dns/include/dns/diff.h b/lib/dns/include/dns/diff.h index 84a20b2b8d..db37565d67 100644 --- a/lib/dns/include/dns/diff.h +++ b/lib/dns/include/dns/diff.h @@ -60,14 +60,6 @@ * timeexpire. */ -typedef enum { - DNS_DIFFOP_ADD = 0, /*%< Add an RR. */ - DNS_DIFFOP_DEL = 1, /*%< Delete an RR. */ - DNS_DIFFOP_EXISTS = 2, /*%< Assert RR existence. */ - DNS_DIFFOP_ADDRESIGN = 4, /*%< ADD + RESIGN. */ - DNS_DIFFOP_DELRESIGN = 5 /*%< DEL + RESIGN. */ -} dns_diffop_t; - typedef struct dns_difftuple dns_difftuple_t; typedef ISC_LIST(dns_difftuple_t) dns_difftuplelist_t; @@ -266,6 +258,37 @@ dns_diff_applysilently(const dns_diff_t *diff, dns_db_t *db, * */ +typedef struct { + dns_db_t *db; + dns_dbversion_t *ver; + bool warn; +} dns_updatectx_t; + +isc_result_t +dns_diff_apply_with_callbacks(const dns_diff_t *diff, + dns_rdatacallbacks_t *callbacks); +/*%< + * Apply 'diff' to the database using the provided callbacks and context. + * The context contains the database, version, and warning flag. + * This allows for custom callback implementations. + * + * Requires: + *\li 'callbacks' points to a valid dns_rdatacallbacks_t structure + *\li 'callbacks->update' is not NULL + */ + +isc_result_t +update_callback(void *arg, const dns_name_t *name, dns_rdataset_t *rds, + dns_diffop_t op DNS__DB_FLARG); +/*%< + * Standard update callback for dns_rdatacallbacks_t. + * Updates a database version by applying DNS record operations. + * Used with dns_updatectx_t context. + * + * Requires: + *\li 'arg' is a valid dns_updatectx_t pointer + */ + isc_result_t dns_diff_load(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks); /*%< diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h index 0dc54cf58e..c52ff87dff 100644 --- a/lib/dns/include/dns/rdataset.h +++ b/lib/dns/include/dns/rdataset.h @@ -691,4 +691,17 @@ dns_trust_totext(dns_trust_t trust); * Display trust in textual form. */ +isc_stdtime_t +dns_rdataset_minresign(dns_rdataset_t *rdataset); +/*%< + * Return the minimum resign time from an RRSIG rdataset. + * + * This function iterates through all RRSIG records in the rdataset + * and returns the earliest expiration time, which indicates when + * the signatures should be resigned. + * + * Requires: + * \li 'rdataset' is a valid rdataset. + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 6cd5085478..992bfceef5 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -182,6 +182,14 @@ typedef struct dst_gssapi_signverifyctx dst_gssapi_signverifyctx_t; typedef enum { dns_hash_sha1 = 1 } dns_hash_t; +typedef enum { + DNS_DIFFOP_ADD = 0, /*%< Add an RR. */ + DNS_DIFFOP_DEL = 1, /*%< Delete an RR. */ + DNS_DIFFOP_EXISTS = 2, /*%< Assert RR existence. */ + DNS_DIFFOP_ADDRESIGN = 4, /*%< ADD + RESIGN. */ + DNS_DIFFOP_DELRESIGN = 5 /*%< DEL + RESIGN. */ +} dns_diffop_t; + typedef enum { dns_fwdpolicy_none = 0, dns_fwdpolicy_first = 1, @@ -433,8 +441,8 @@ typedef void (*dns_loaddonefunc_t)(void *, isc_result_t); typedef void (*dns_rawdatafunc_t)(dns_zone_t *, dns_masterrawheader_t *); typedef isc_result_t (*dns_addrdatasetfunc_t)(void *arg, const dns_name_t *name, - dns_rdataset_t *rdataset - DNS__DB_FLARG); + dns_rdataset_t *rdataset, + dns_diffop_t op DNS__DB_FLARG); typedef void (*dns_transactionfunc_t)(void *arg); typedef isc_result_t (*dns_additionaldatafunc_t)( diff --git a/lib/dns/master.c b/lib/dns/master.c index ec5e7c41e3..2fed2999f8 100644 --- a/lib/dns/master.c +++ b/lib/dns/master.c @@ -511,7 +511,7 @@ loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, unsigned int options, REQUIRE(lctxp != NULL && *lctxp == NULL); REQUIRE(callbacks != NULL); - REQUIRE(callbacks->add != NULL); + REQUIRE(callbacks->update != NULL); REQUIRE(callbacks->error != NULL); REQUIRE(callbacks->warn != NULL); REQUIRE(mctx != NULL); @@ -2948,8 +2948,9 @@ commit(dns_rdatacallbacks_t *callbacks, dns_loadctx_t *lctx, dataset.attributes |= DNS_RDATASETATTR_RESIGN; dataset.resign = resign_fromlist(this, lctx); } - result = callbacks->add(callbacks->add_private, owner, - &dataset DNS__DB_FILELINE); + result = callbacks->update(callbacks->add_private, owner, + &dataset, + DNS_DIFFOP_ADD DNS__DB_FILELINE); if (result == ISC_R_NOMEMORY) { (*error)(callbacks, "dns_master_load: %s", isc_result_totext(result)); diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c index cd576bc33c..ae09403435 100644 --- a/lib/dns/qpzone.c +++ b/lib/dns/qpzone.c @@ -43,6 +43,8 @@ #include #include #include +#include +#include #include #include #include @@ -102,6 +104,17 @@ typedef struct qpzonedb qpzonedb_t; typedef struct qpznode qpznode_t; +/* + * Qpzone-specific update context that extends dns_updatectx_t, used in IXFR. + */ +typedef struct qpzone_updatectx { + dns_updatectx_t base; + dns_qp_t *qp; + dns_qp_t *nsec; + dns_qp_t *nsec3; + dns_qpread_t qpr; +} qpzone_updatectx_t; + typedef struct qpz_changed { qpznode_t *node; bool dirty; @@ -2045,8 +2058,8 @@ addwildcards(qpzonedb_t *qpdb, dns_qp_t *qp, const dns_name_t *name) { } static isc_result_t -loading_addrdataset(void *arg, const dns_name_t *name, - dns_rdataset_t *rdataset DNS__DB_FLARG) { +loading_addrdataset(void *arg, const dns_name_t *name, dns_rdataset_t *rdataset, + dns_diffop_t op ISC_ATTR_UNUSED DNS__DB_FLARG) { qpz_load_t *loadctx = arg; qpzonedb_t *qpdb = (qpzonedb_t *)loadctx->db; qpznode_t *node = NULL; @@ -2191,7 +2204,7 @@ beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { RWUNLOCK(&qpdb->lock, isc_rwlocktype_write); - callbacks->add = loading_addrdataset; + callbacks->update = loading_addrdataset; callbacks->setup = loading_setup; callbacks->commit = loading_commit; callbacks->add_private = loadctx; @@ -2226,7 +2239,7 @@ endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { RWUNLOCK(&qpdb->lock, isc_rwlocktype_write); } - callbacks->add = NULL; + callbacks->update = NULL; callbacks->setup = NULL; callbacks->commit = NULL; callbacks->add_private = NULL; @@ -2434,37 +2447,82 @@ setgluecachestats(dns_db_t *db, isc_stats_t *stats) { return ISC_R_SUCCESS; } -static isc_result_t -findnodeintree(qpzonedb_t *qpdb, const dns_name_t *name, bool create, - bool nsec3, dns_dbnode_t **nodep DNS__DB_FLARG) { - isc_result_t result; - qpznode_t *node = NULL; - dns_qpmulti_t *dbtree = nsec3 ? qpdb->nsec3 : qpdb->tree; - dns_qpread_t qpr = { 0 }; +static dns_qp_t * +begin_transaction(dns_qpmulti_t *dbtree, dns_qpread_t *qprp, bool create) { dns_qp_t *qp = NULL; if (create) { dns_qpmulti_write(dbtree, &qp); } else { - dns_qpmulti_query(dbtree, &qpr); - qp = (dns_qp_t *)&qpr; + dns_qpmulti_query(dbtree, qprp); + qp = (dns_qp_t *)qprp; } - result = dns_qp_getname(qp, name, (void **)&node, NULL); - if (result != ISC_R_SUCCESS) { - if (!create) { - dns_qpread_destroy(dbtree, &qpr); - return result; - } + return qp; +} +static void +end_transaction(dns_qpmulti_t *dbtree, dns_qp_t *qp, bool create) { + if (create) { + dns_qp_compact(qp, DNS_QPGC_MAYBE); + dns_qpmulti_commit(dbtree, &qp); + } else { + dns_qpread_t *qprp = (dns_qpread_t *)qp; + dns_qpread_destroy(dbtree, qprp); + } +} + +static isc_result_t +findnodeintree(qpzonedb_t *qpdb, dns_qp_t *qp, const dns_name_t *name, + bool create, bool nsec3, dns_dbnode_t **nodep DNS__DB_FLARG) { + isc_result_t result; + qpznode_t *node = NULL; + + /* + * findnodeintree is a wrapper around dns_qp_getname that does some + * qpzone-specific bookkeeping before returning the lookup result to the + * caller. + * + * First, we do a lookup ... + */ + result = dns_qp_getname(qp, name, (void **)&node, NULL); + if (result == ISC_R_SUCCESS) { + /* + * ... if the lookup is successful, we need to increase both the + * internal and external reference count before returning to + * the caller. qpznode_acquire takes care of that. + */ + qpznode_acquire(qpdb, node DNS__DB_FLARG_PASS); + } else if (result != ISC_R_SUCCESS && create) { + /* + * ... if the lookup is unsuccessful, but the caller asked us to + * create a new node, create one and insert it into the tree. + */ node = new_qpznode(qpdb, name); + result = dns_qp_insert(qp, node, 0); INSIST(result == ISC_R_SUCCESS); - qpznode_unref(node); + + /* + * The new node now has two internal references: + * - One from new_qpznode, that initializes references at 1. + * - One from attach_leaf, that increases the reference by + * one at insertion in the qp-tree. + * We want the node to have two internal and one external + * reference: + * - One internal reference from the qp-tree. + * - One internal and one external reference from the caller. + * + * So we increase the external reference count by one. + */ + qpznode_erefs_increment(qpdb, node DNS__DB_FLARG_PASS); if (nsec3) { node->nsec = DNS_DB_NSEC_NSEC3; } else { + /* + * Add empty non-terminal nodes to help with wildcards. + */ addwildcards(qpdb, qp, name); if (dns_name_iswildcard(name)) { wildcardmagic(qpdb, qp, name); @@ -2472,20 +2530,15 @@ findnodeintree(qpzonedb_t *qpdb, const dns_name_t *name, bool create, } } - INSIST(node->nsec == DNS_DB_NSEC_NSEC3 || !nsec3); - - qpznode_acquire(qpdb, node DNS__DB_FLARG_PASS); - - if (create) { - dns_qp_compact(qp, DNS_QPGC_MAYBE); - dns_qpmulti_commit(dbtree, &qp); - } else { - dns_qpread_destroy(dbtree, &qpr); - } + /* + * ... if the lookup is unsuccessful, and the caller didn't ask us + * to create a new node, there is nothing to do. Return the result + * of the lookup to the caller, and set *nodep to NULL + */ *nodep = (dns_dbnode_t *)node; - return ISC_R_SUCCESS; + return result; } static isc_result_t @@ -2495,8 +2548,15 @@ findnode(dns_db_t *db, const dns_name_t *name, bool create, REQUIRE(VALID_QPZONE(qpdb)); - return findnodeintree(qpdb, name, create, false, - nodep DNS__DB_FLARG_PASS); + dns_qpread_t qpr = { 0 }; + dns_qp_t *qp = begin_transaction(qpdb->tree, &qpr, create); + + isc_result_t result = findnodeintree(qpdb, qp, name, create, false, + nodep DNS__DB_FLARG_PASS); + + end_transaction(qpdb->tree, qp, create); + + return result; } static isc_result_t @@ -2506,8 +2566,15 @@ findnsec3node(dns_db_t *db, const dns_name_t *name, bool create, REQUIRE(VALID_QPZONE(qpdb)); - return findnodeintree(qpdb, name, create, true, - nodep DNS__DB_FLARG_PASS); + dns_qpread_t qpr = { 0 }; + dns_qp_t *qp = begin_transaction(qpdb->nsec3, &qpr, create); + + isc_result_t result = findnodeintree(qpdb, qp, name, create, true, + nodep DNS__DB_FLARG_PASS); + + end_transaction(qpdb->nsec3, qp, create); + + return result; } static bool @@ -4646,10 +4713,21 @@ createiterator(dns_db_t *db, unsigned int options, return ISC_R_SUCCESS; } +/* + * Main logic to add a new rdataset to a node. + * + * The reason this is split from qpzone_addrdataset is to allow the reuse of + * the same qp transaction for multiple adds. + * + * qpzone_subtractrdataset doesn't have the same problem since it cannot delete + * nodes, only rdatasets. + */ static isc_result_t -addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, - isc_stdtime_t now ISC_ATTR_UNUSED, dns_rdataset_t *rdataset, - unsigned int options, dns_rdataset_t *addedrdataset DNS__DB_FLARG) { +qpzone_addrdataset_inner(dns_db_t *db, dns_dbnode_t *dbnode, + dns_dbversion_t *dbversion, + isc_stdtime_t now ISC_ATTR_UNUSED, + dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *addedrdataset, dns_qp_t *nsec) { isc_result_t result; qpzonedb_t *qpdb = (qpzonedb_t *)db; qpznode_t *node = (qpznode_t *)dbnode; @@ -4660,7 +4738,6 @@ addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, isc_rwlock_t *nlock = NULL; dns_fixedname_t fn; dns_name_t *name = dns_fixedname_initname(&fn); - dns_qp_t *nsec = NULL; REQUIRE(VALID_QPZONE(qpdb)); REQUIRE(version != NULL && version->qpdb == qpdb); @@ -4721,11 +4798,10 @@ addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, /* * Add to the auxiliary NSEC tree if we're adding an NSEC record. */ - if (node->nsec != DNS_DB_NSEC_HAS_NSEC && - rdataset->type == dns_rdatatype_nsec) - { - dns_qpmulti_write(qpdb->nsec, &nsec); - } + + bool is_nsec = node->nsec != DNS_DB_NSEC_HAS_NSEC && + rdataset->type == dns_rdatatype_nsec; + REQUIRE(!is_nsec || nsec != NULL); /* * If we're adding a delegation type or adding to the auxiliary NSEC @@ -4741,7 +4817,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, NODE_WRLOCK(nlock, &nlocktype); result = ISC_R_SUCCESS; - if (nsec != NULL) { + + if (is_nsec) { node->nsec = DNS_DB_NSEC_HAS_NSEC; /* @@ -4772,7 +4849,34 @@ addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, NODE_UNLOCK(nlock, &nlocktype); - if (nsec != NULL) { + return result; +} + +static isc_result_t +addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, + isc_stdtime_t now ISC_ATTR_UNUSED, dns_rdataset_t *rdataset, + unsigned int options, dns_rdataset_t *addedrdataset DNS__DB_FLARG) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + qpznode_t *node = (qpznode_t *)dbnode; + dns_qp_t *nsec = NULL; + + REQUIRE(VALID_QPZONE(qpdb)); + + bool is_nsec = node->nsec != DNS_DB_NSEC_HAS_NSEC && + rdataset->type == dns_rdatatype_nsec; + + /* + * Add to the auxiliary NSEC tree if we're adding an NSEC record. + */ + if (is_nsec) { + dns_qpmulti_write(qpdb->nsec, &nsec); + } + + isc_result_t result = qpzone_addrdataset_inner(db, dbnode, dbversion, + now, rdataset, options, + addedrdataset, nsec); + + if (is_nsec) { dns_qpmulti_commit(qpdb->nsec, &nsec); } @@ -5202,10 +5306,186 @@ setmaxtypepername(dns_db_t *db, uint32_t value) { qpdb->maxtypepername = value; } +/* + * Qpzone specialization of the update function from dns_rdatacallbacks_t, + * meant to reuse the same qp transaction for multiple operations. + */ +static isc_result_t +qpzone_update_rdataset(qpzonedb_t *qpdb, qpz_version_t *version, + qpzone_updatectx_t *ctx, dns_name_t *name, + dns_rdataset_t *rds, dns_diffop_t op) { + isc_result_t result; + unsigned int options; + dns_rdataset_t ardataset; + dns_dbnode_t *node = NULL; + bool is_nsec3; + + dns_rdataset_init(&ardataset); + + is_nsec3 = (rds->type == dns_rdatatype_nsec3 || + rds->covers == dns_rdatatype_nsec3); + dns_qp_t *dbtree = is_nsec3 ? ctx->nsec3 : ctx->qp; + + result = findnodeintree(qpdb, dbtree, name, true, is_nsec3, + &node DNS__DB_FLARG_PASS); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + switch (op) { + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + /* + * See the comment on qpzone_addrdataset_inner for why we + * cannot use qpzone_addrdataset directly. + */ + options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | + DNS_DBADD_EXACTTTL; + result = qpzone_addrdataset_inner( + (dns_db_t *)qpdb, node, (dns_dbversion_t *)version, 0, + rds, options, &ardataset, ctx->nsec DNS__DB_FLARG_PASS); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UNCHANGED: + case DNS_R_NXRRSET: + dns_rdataset_setownercase(&ardataset, name); + CHECK(result); + break; + default: + CHECK(result); + break; + } + break; + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; + result = subtractrdataset( + (dns_db_t *)qpdb, node, (dns_dbversion_t *)version, rds, + options, &ardataset DNS__DB_FLARG_PASS); + break; + default: + UNREACHABLE(); + } + + bool is_resign = rds->type == dns_rdatatype_rrsig && + (op == DNS_DIFFOP_DELRESIGN || + op == DNS_DIFFOP_ADDRESIGN); + + if (result == ISC_R_SUCCESS && is_resign) { + isc_stdtime_t resign; + resign = dns_rdataset_minresign(&ardataset); + dns_db_setsigningtime((dns_db_t *)qpdb, &ardataset, resign); + } + +cleanup: + if (node != NULL) { + dns_db_detachnode((dns_db_t *)qpdb, &node); + } + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + return result; +} + +static isc_result_t +qpzone_update_callback(void *arg, const dns_name_t *name, dns_rdataset_t *rds, + dns_diffop_t op DNS__DB_FLARG) { + qpzone_updatectx_t *ctx = arg; + qpzonedb_t *qpdb = (qpzonedb_t *)ctx->base.db; + qpz_version_t *version = (qpz_version_t *)ctx->base.ver; + + return qpzone_update_rdataset(qpdb, version, ctx, (dns_name_t *)name, + rds, op); +} + +static isc_result_t +qpzone_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + + REQUIRE(VALID_QPZONE(qpdb)); + REQUIRE(ver != NULL); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + qpzone_updatectx_t *ctx = isc_mem_get(qpdb->common.mctx, sizeof(*ctx)); + *ctx = (qpzone_updatectx_t){ + .base.db = db, + .base.ver = ver, + .base.warn = true, + .qpr = (dns_qpread_t){ 0 }, + }; + ctx->qp = begin_transaction(qpdb->tree, &ctx->qpr, true); + ctx->nsec = begin_transaction(qpdb->nsec, &ctx->qpr, true); + ctx->nsec3 = begin_transaction(qpdb->nsec3, &ctx->qpr, true); + + callbacks->update = qpzone_update_callback; + callbacks->add_private = ctx; + + return ISC_R_SUCCESS; +} + +static isc_result_t +qpzone_commitupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + qpzone_updatectx_t *ctx; + + REQUIRE(VALID_QPZONE(qpdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + ctx = (qpzone_updatectx_t *)callbacks->add_private; + if (ctx != NULL) { + end_transaction(qpdb->nsec3, ctx->nsec3, true); + end_transaction(qpdb->nsec, ctx->nsec, true); + end_transaction(qpdb->tree, ctx->qp, true); + + isc_mem_put(qpdb->common.mctx, ctx, sizeof(*ctx)); + /* + * We need to allow the context to be committed or aborted + * multiple times, so we set the callback data to NULL + * to signal that nothing needs to be done with this context. + */ + callbacks->add_private = NULL; + } + + return ISC_R_SUCCESS; +} + +/* + * For now, abortupdate needs to *commit* the results, so that closeversion + * cleanup might work. + */ +static isc_result_t +qpzone_abortupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + qpzone_updatectx_t *ctx; + + REQUIRE(VALID_QPZONE(qpdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + ctx = (qpzone_updatectx_t *)callbacks->add_private; + if (ctx != NULL) { + end_transaction(qpdb->nsec3, ctx->nsec3, true); + end_transaction(qpdb->nsec, ctx->nsec, true); + end_transaction(qpdb->tree, ctx->qp, true); + + isc_mem_put(qpdb->common.mctx, ctx, sizeof(*ctx)); + /* + * See qpzone_commitupdate. + */ + callbacks->add_private = NULL; + } + + return ISC_R_SUCCESS; +} + static dns_dbmethods_t qpdb_zonemethods = { .destroy = qpdb_destroy, .beginload = beginload, .endload = endload, + .beginupdate = qpzone_beginupdate, + .commitupdate = qpzone_commitupdate, + .abortupdate = qpzone_abortupdate, .currentversion = currentversion, .newversion = newversion, .attachversion = attachversion, diff --git a/lib/dns/rbt-zonedb.c b/lib/dns/rbt-zonedb.c index dcb2afad78..98c1d6ba18 100644 --- a/lib/dns/rbt-zonedb.c +++ b/lib/dns/rbt-zonedb.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #include #include "db_p.h" +#include "isc/attributes.h" #include "rbtdb_p.h" #define EXISTS(header) \ @@ -1677,8 +1679,8 @@ done: } static isc_result_t -loading_addrdataset(void *arg, const dns_name_t *name, - dns_rdataset_t *rdataset DNS__DB_FLARG) { +loading_addrdataset(void *arg, const dns_name_t *name, dns_rdataset_t *rdataset, + dns_diffop_t op ISC_ATTR_UNUSED DNS__DB_FLARG) { rbtdb_load_t *loadctx = arg; dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)loadctx->db; dns_rbtnode_t *node = NULL; @@ -1812,7 +1814,7 @@ beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { RWUNLOCK(&rbtdb->lock, isc_rwlocktype_write); - callbacks->add = loading_addrdataset; + callbacks->update = loading_addrdataset; callbacks->add_private = loadctx; return ISC_R_SUCCESS; @@ -1849,7 +1851,7 @@ endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { RWUNLOCK(&rbtdb->lock, isc_rwlocktype_write); } - callbacks->add = NULL; + callbacks->update = NULL; callbacks->add_private = NULL; isc_mem_put(rbtdb->common.mctx, loadctx, sizeof(*loadctx)); @@ -2247,10 +2249,52 @@ addglue(dns_db_t *db, dns_dbversion_t *dbversion, dns_rdataset_t *rdataset, return ISC_R_SUCCESS; } +static isc_result_t +rbt_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(ver != NULL); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + dns_updatectx_t *ctx = isc_mem_get(rbtdb->common.mctx, sizeof(*ctx)); + *ctx = (dns_updatectx_t){ + .db = db, + .ver = ver, + .warn = true, + }; + + callbacks->update = update_callback; + callbacks->add_private = ctx; + + return ISC_R_SUCCESS; +} + +static isc_result_t +rbt_endupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_updatectx_t *ctx; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + ctx = (dns_updatectx_t *)callbacks->add_private; + if (ctx != NULL) { + isc_mem_put(rbtdb->common.mctx, ctx, sizeof(*ctx)); + callbacks->add_private = NULL; + } + + return ISC_R_SUCCESS; +} + dns_dbmethods_t dns__rbtdb_zonemethods = { .destroy = dns__rbtdb_destroy, .beginload = beginload, .endload = endload, + .beginupdate = rbt_beginupdate, + .commitupdate = rbt_endupdate, + .abortupdate = rbt_endupdate, .currentversion = dns__rbtdb_currentversion, .newversion = dns__rbtdb_newversion, .attachversion = dns__rbtdb_attachversion, diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c index b6043ba7ea..dadb1aeca5 100644 --- a/lib/dns/rdataset.c +++ b/lib/dns/rdataset.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include static const char *trustnames[] = { "none", "pending-additional", @@ -676,3 +678,41 @@ dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, rdataset->ttl = ttl; sigrdataset->ttl = ttl; } + +isc_stdtime_t +dns_rdataset_minresign(dns_rdataset_t *rdataset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + int64_t when; + isc_result_t result; + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + result = dns_rdataset_first(rdataset); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + (void)dns_rdata_tostruct(&rdata, &sig, NULL); + if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { + when = 0; + } else { + when = dns_time64_from32(sig.timeexpire); + } + dns_rdata_reset(&rdata); + + result = dns_rdataset_next(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + (void)dns_rdata_tostruct(&rdata, &sig, NULL); + if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { + goto next_rr; + } + if (when == 0 || dns_time64_from32(sig.timeexpire) < when) { + when = dns_time64_from32(sig.timeexpire); + } + next_rr: + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); + } + INSIST(result == ISC_R_NOMORE); + return (isc_stdtime_t)when; +} diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 19395907bf..af717462ca 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -78,6 +78,8 @@ typedef enum { * Incoming zone transfer context. */ +typedef struct dns_ixfr dns_ixfr_t; + struct dns_xfrin { unsigned int magic; isc_mem_t *mctx; @@ -170,7 +172,7 @@ struct dns_xfrin { */ dns_rdatacallbacks_t axfr; - struct { + struct dns_ixfr { uint32_t request_serial; uint32_t current_serial; dns_journal_t *journal; @@ -485,24 +487,22 @@ cleanup: } static isc_result_t -ixfr_begin_transaction(dns_xfrin_t *xfr) { +ixfr_begin_transaction(dns_ixfr_t *ixfr) { isc_result_t result = ISC_R_SUCCESS; - if (xfr->ixfr.journal != NULL) { - CHECK(dns_journal_begin_transaction(xfr->ixfr.journal)); + if (ixfr->journal != NULL) { + CHECK(dns_journal_begin_transaction(ixfr->journal)); } cleanup: return result; } static isc_result_t -ixfr_end_transaction(dns_xfrin_t *xfr) { +ixfr_end_transaction(dns_ixfr_t *ixfr) { isc_result_t result = ISC_R_SUCCESS; - - CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); /* XXX enter ready-to-commit state here */ - if (xfr->ixfr.journal != NULL) { - CHECK(dns_journal_commit(xfr->ixfr.journal)); + if (ixfr->journal != NULL) { + CHECK(dns_journal_commit(ixfr->journal)); } cleanup: return result; @@ -513,9 +513,14 @@ ixfr_apply_one(dns_xfrin_t *xfr, ixfr_apply_data_t *data) { isc_result_t result = ISC_R_SUCCESS; uint64_t records; - CHECK(ixfr_begin_transaction(xfr)); + dns_rdatacallbacks_t callbacks; + dns_rdatacallbacks_init(&callbacks); - CHECK(dns_diff_apply(&data->diff, xfr->db, xfr->ver)); + CHECK(ixfr_begin_transaction(&xfr->ixfr)); + + dns_db_beginupdate(xfr->db, xfr->ver, &callbacks); + + CHECK(dns_diff_apply_with_callbacks(&data->diff, &callbacks)); if (xfr->maxrecords != 0U) { result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL); if (result == ISC_R_SUCCESS && records > xfr->maxrecords) { @@ -526,12 +531,29 @@ ixfr_apply_one(dns_xfrin_t *xfr, ixfr_apply_data_t *data) { CHECK(dns_journal_writediff(xfr->ixfr.journal, &data->diff)); } - result = ixfr_end_transaction(xfr); + /* + * At the moment, rdatacallbacks doesn't offer a way to inspect the + * result of a transaction before committing it. + * + * So we need to commit *before* calling dns_zone_verifydb, and rely + * on closeversion to actually do cleanup. + */ + CHECK(dns_db_commitupdate(xfr->db, &callbacks)); + + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + + result = ixfr_end_transaction(&xfr->ixfr); return result; cleanup: + /* + * For the reason stated above, dns_db_abortupdate must *commit* the + * changes and rely on closeversion to clean them up. + */ + (void)dns_db_abortupdate(xfr->db, &callbacks); + /* We need to end the transaction, but keep the previous error */ - (void)ixfr_end_transaction(xfr); + (void)ixfr_end_transaction(&xfr->ixfr); return result; } diff --git a/tests/dns/master_test.c b/tests/dns/master_test.c index 5c1a9bcc0b..80403412ce 100644 --- a/tests/dns/master_test.c +++ b/tests/dns/master_test.c @@ -63,13 +63,14 @@ static void rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *header); static isc_result_t -add_callback(void *arg, const dns_name_t *owner, - dns_rdataset_t *dataset DNS__DB_FLARG) { +add_callback(void *arg, const dns_name_t *owner, dns_rdataset_t *dataset, + dns_diffop_t op DNS__DB_FLARG) { char buf[BIGBUFLEN]; isc_buffer_t target; isc_result_t result; UNUSED(arg); + UNUSED(op); isc_buffer_init(&target, buf, BIGBUFLEN); result = dns_rdataset_totext(dataset, owner, false, false, &target); @@ -107,7 +108,7 @@ setup_master(void (*warn)(struct dns_rdatacallbacks *, const char *, ...), } dns_rdatacallbacks_init_stdio(&callbacks); - callbacks.add = add_callback; + callbacks.update = add_callback; callbacks.rawdata = rawdata_callback; callbacks.zone = NULL; if (warn != NULL) { @@ -133,7 +134,7 @@ test_master(const char *workdir, const char *testfile, } dns_rdatacallbacks_init_stdio(&callbacks); - callbacks.add = add_callback; + callbacks.update = add_callback; callbacks.rawdata = rawdata_callback; callbacks.zone = NULL; if (warn != NULL) { diff --git a/tests/dns/qpzone_test.c b/tests/dns/qpzone_test.c index 5f7de92cbd..02790bba23 100644 --- a/tests/dns/qpzone_test.c +++ b/tests/dns/qpzone_test.c @@ -25,6 +25,8 @@ #include +#include +#include #include #include #include @@ -45,6 +47,22 @@ ((atomic_load_acquire(&(header)->attributes) & \ DNS_SLABHEADERATTR_CASESET) != 0) +/* + * Macro that uses a for loop to execute a cleanup at the end of scope. + */ +#define WITH_NEWVERSION(db, version_var, should_commit) \ + for (dns_dbversion_t *version_var = NULL, *_tmp = ({ \ + isc_result_t _result = dns_db_newversion(db, \ + &version_var); \ + assert_int_equal(_result, ISC_R_SUCCESS); \ + (dns_dbversion_t *)1; \ + }); \ + _tmp != NULL; \ + _tmp = ({ \ + dns_db_closeversion(db, &version_var, should_commit); \ + (dns_dbversion_t *)NULL; \ + })) + const char *ownercase_vectors[12][2] = { { "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", @@ -96,6 +114,70 @@ const char *ownercase_vectors[12][2] = { } }; +static unsigned char example_org_data[] = { 7, 'e', 'x', 'a', 'm', 'p', 'l', + 'e', 3, 'o', 'r', 'g', 0 }; +static unsigned char example_org_offsets[] = { 0, 8, 12 }; +static dns_name_t example_org_name = DNS_NAME_INITABSOLUTE(example_org_data, + example_org_offsets); + +/* IPv6 test addresses */ +static unsigned char aaaa_test_data[][16] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, /* ::1 */ + { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2 } /* 2001:db8::2 + */ +}; + +/* RRSIG test signatures */ +static unsigned char rrsig_signature1[64] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40 +}; + +static unsigned char rrsig_signature2[64] = { + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80 +}; + +/* RRSIG test structures */ +static dns_rdata_rrsig_t rrsig_test_data1 = { + .common = { .rdtype = dns_rdatatype_rrsig, + .rdclass = dns_rdataclass_in }, + .covered = dns_rdatatype_a, + .algorithm = DST_ALG_RSASHA256, + .labels = 2, + .originalttl = 300, + .timeexpire = 1695820800, + .timesigned = 1695744000, + .keyid = 0x1234, + .signer = DNS_NAME_INITABSOLUTE(example_org_data, example_org_offsets), + .siglen = 64, + .signature = rrsig_signature1, +}; + +static dns_rdata_rrsig_t rrsig_test_data2 = { + .common = { .rdtype = dns_rdatatype_rrsig, + .rdclass = dns_rdataclass_in }, + .covered = dns_rdatatype_a, + .algorithm = DST_ALG_RSASHA256, + .labels = 2, + .originalttl = 300, + .timeexpire = 1695820800, + .timesigned = 1695744000, + .keyid = 0x5678, + .signer = DNS_NAME_INITABSOLUTE(example_org_data, example_org_offsets), + .siglen = 64, + .signature = rrsig_signature2, +}; + static bool ownercase_test_one(const char *str1, const char *str2) { isc_result_t result; @@ -165,6 +247,118 @@ ISC_RUN_TEST_IMPL(ownercase) { assert_false(ownercase_test_one("\\216", "\\246")); } +static ssize_t +find_ip_index(const unsigned char *target_ip, unsigned char (*ips)[16], + ssize_t count) { + for (ssize_t i = 0; i < count; i++) { + if (memcmp(target_ip, ips[i], 16) == 0) { + return i; + } + } + return -1; +} + +static isc_result_t +apply_dns_update(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, + dns_rdatatype_t rdtype, dns_rdataclass_t rdclass, uint32_t ttl, + const unsigned char *data, size_t data_len, dns_diffop_t op) { + isc_result_t result; + dns_rdatacallbacks_t callbacks; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdatacallbacks_init(&callbacks); + + result = dns_db_beginupdate(db, version, &callbacks); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Set rdata fields directly without reinitializing */ + rdata.data = (unsigned char *)data; + rdata.length = data_len; + rdata.rdclass = rdclass; + rdata.type = rdtype; + + dns_rdatalist_init(&rdatalist); + rdatalist.ttl = ttl; + rdatalist.type = rdtype; + rdatalist.rdclass = rdclass; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + dns_rdatalist_tordataset(&rdatalist, &rdataset); + + isc_result_t callback_result = callbacks.update(callbacks.add_private, + name, &rdataset, op); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_rdataset_disassociate(&rdataset); + + result = dns_db_commitupdate(db, &callbacks); + assert_int_equal(result, ISC_R_SUCCESS); + + return callback_result; +} + +static void +verify_aaaa_records(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, unsigned char (*ips)[16], + ssize_t expected_count, uint32_t expected_ttl) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + bool *found_ips = NULL; + dns_fixedname_t found_fname; + dns_name_t *found_name = dns_fixedname_initname(&found_fname); + + /* Allocate zero-initialized found flags array */ + found_ips = isc_mem_cget(mctx, (size_t)expected_count, sizeof(bool)); + + dns_rdataset_init(&rdataset); + + result = dns_db_find(db, name, version, dns_rdatatype_aaaa, 0, 0, &node, + found_name, &rdataset, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Check rdataset metadata */ + assert_int_equal(rdataset.type, dns_rdatatype_aaaa); + assert_int_equal(rdataset.rdclass, dns_rdataclass_in); + assert_int_equal(rdataset.ttl, expected_ttl); + + /* Iterate through all AAAA records */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + + /* Verify this is a valid IPv6 address */ + assert_int_equal(rdata.length, 16); + + /* + * Find whether the IP is in our expected list, and detect + * duplicates. Index will be -1 if the IP is not found. + */ + ssize_t index = find_ip_index(rdata.data, ips, expected_count); + assert_true(index >= 0); + assert_false(found_ips[index]); + found_ips[index] = true; + } + + /* Count found IPs by summing overt the boolean array */ + ssize_t found_count = 0; + for (ssize_t i = 0; i < expected_count; i++) { + found_count += found_ips[i]; + } + + /* Verify we found exactly the expected number of records */ + assert_int_equal(found_count, expected_count); + + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + isc_mem_cput(mctx, found_ips, (size_t)expected_count, sizeof(bool)); +} + ISC_RUN_TEST_IMPL(setownercase) { isc_result_t result; uint8_t qpdb_s[sizeof(qpzonedb_t) + sizeof(qpzone_bucket_t)]; @@ -220,9 +414,122 @@ ISC_RUN_TEST_IMPL(setownercase) { assert_true(dns_name_caseequal(name1, name2)); } +ISC_RUN_TEST_IMPL(diffop_add_sub) { + isc_result_t result; + dns_db_t *db = NULL; + + result = dns__qpzone_create(mctx, &example_org_name, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(db); + + WITH_NEWVERSION(db, version, true) { + apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, 300, + aaaa_test_data[0], 16, DNS_DIFFOP_ADD); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, + 300, aaaa_test_data[1], 16, + DNS_DIFFOP_ADD); + assert_int_equal(result, ISC_R_SUCCESS); + + verify_aaaa_records(db, version, &example_org_name, + aaaa_test_data, 2, 300); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, + 300, aaaa_test_data[0], 16, + DNS_DIFFOP_DEL); + assert_int_equal(result, ISC_R_SUCCESS); + + verify_aaaa_records(db, version, &example_org_name, + &aaaa_test_data[1], 1, 300); + } + + WITH_NEWVERSION(db, version, false) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, + 600, aaaa_test_data[0], 16, + DNS_DIFFOP_ADD); + assert_int_equal(result, DNS_R_NOTEXACT); + } + + dns_db_detach(&db); + assert_null(db); +} + +ISC_RUN_TEST_IMPL(diffop_addresign) { + isc_result_t result; + dns_db_t *db = NULL; + + /* Create RRSIG structures and convert to wire format */ + dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT; + isc_buffer_t buffer1, buffer2; + unsigned char rrsig_data1[512], rrsig_data2[512]; + + isc_buffer_init(&buffer1, rrsig_data1, sizeof(rrsig_data1)); + result = dns_rdata_fromstruct(&rdata1, dns_rdataclass_in, + dns_rdatatype_rrsig, &rrsig_test_data1, + &buffer1); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&buffer2, rrsig_data2, sizeof(rrsig_data2)); + result = dns_rdata_fromstruct(&rdata2, dns_rdataclass_in, + dns_rdatatype_rrsig, &rrsig_test_data2, + &buffer2); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns__qpzone_create(mctx, &example_org_name, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(db); + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata1.data, + rdata1.length, DNS_DIFFOP_ADDRESIGN); + assert_int_equal(result, ISC_R_SUCCESS); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata2.data, + rdata2.length, DNS_DIFFOP_ADDRESIGN); + assert_int_equal(result, ISC_R_SUCCESS); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata1.data, + rdata1.length, DNS_DIFFOP_DELRESIGN); + assert_int_equal(result, ISC_R_SUCCESS); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata2.data, + rdata2.length, DNS_DIFFOP_DELRESIGN); + assert_int_equal(result, DNS_R_NXRRSET); + } + + dns_db_detach(&db); + assert_null(db); +} + ISC_TEST_LIST_START ISC_TEST_ENTRY(ownercase) ISC_TEST_ENTRY(setownercase) +ISC_TEST_ENTRY(diffop_add_sub) +ISC_TEST_ENTRY(diffop_addresign) ISC_TEST_LIST_END ISC_TEST_MAIN