From f1b2646f2b667563980d374f7f49b09b0aed5b62 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 27 Oct 2025 17:04:56 +0100 Subject: [PATCH 01/13] Update dns_dnssec_sync(update|delete) return code Update the functions 'dns_dnssec_syncupdate()' and 'dns_dnssec_syncdelete()' to make a distinction between a changed RRset and no changes made. The return code will be used later to determine if we need to send a NOTIFY(CDS) to DSYNC endpoints. --- lib/dns/dnssec.c | 109 +++++++++++++++++++++++++++-------- lib/dns/include/dns/dnssec.h | 2 + lib/dns/include/dns/ds.h | 3 + lib/dns/zone.c | 10 +++- 4 files changed, 98 insertions(+), 26 deletions(-) diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 2874341be0..c5be6eda9a 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -1825,8 +1825,9 @@ add_cds(dns_dnsseckey_t *key, dns_rdata_t *keyrdata, const char *keystr, "CDS (%s) for key %s is now published", algbuf, keystr); addrdata(&cdsrdata, diff, origin, ttl, mctx); + return ISC_R_SUCCESS; } - return ISC_R_SUCCESS; + return DNS_R_UNCHANGED; } static isc_result_t @@ -1850,8 +1851,9 @@ delete_cds(dns_dnsseckey_t *key, dns_rdata_t *keyrdata, const char *keystr, "CDS (%s) for key %s is now deleted", algbuf, keystr); delrdata(&cdsrdata, diff, origin, cds->ttl, mctx); + return ISC_R_SUCCESS; } - return ISC_R_SUCCESS; + return DNS_R_UNCHANGED; } isc_result_t @@ -1861,9 +1863,10 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, bool gencdnskey, dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx) { unsigned char keybuf[DST_KEY_MAXSIZE]; - isc_result_t result; + isc_result_t result = DNS_R_UNCHANGED; dns_ttl_t cdsttl = ttl; dns_ttl_t cdnskeyttl = ttl; + bool changed = false; REQUIRE(digests != NULL); REQUIRE(keys != NULL); @@ -1890,9 +1893,15 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, dst_key_format(key->key, keystr, sizeof(keystr)); ISC_LIST_FOREACH(*digests, alg, link) { - CHECK(add_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - alg->digest, cdsttl, diff, mctx)); + result = add_cds(key, &cdnskeyrdata, + (const char *)keystr, cds, + alg->digest, cdsttl, diff, + mctx); + if (result == ISC_R_SUCCESS) { + changed = true; + } else if (result != DNS_R_UNCHANGED) { + goto cleanup; + } } if (gencdnskey && @@ -1906,6 +1915,7 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, keystr); addrdata(&cdnskeyrdata, diff, origin, cdnskeyttl, mctx); + changed = true; } } @@ -1915,15 +1925,32 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, if (dns_rdataset_isassociated(cds)) { /* Delete all possible CDS records */ - delete_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - DNS_DSDIGEST_SHA1, diff, mctx); - delete_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - DNS_DSDIGEST_SHA256, diff, mctx); - delete_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - DNS_DSDIGEST_SHA384, diff, mctx); + for (dns_dsdigest_t digest = DNS_DSDIGEST_SHA1; + digest < DNS_DSDIGEST_TOTAL; digest++) + { + result = delete_cds( + key, &cdnskeyrdata, + (const char *)keystr, cds, + digest, diff, mctx); + + switch (result) { + case ISC_R_SUCCESS: + changed = true; + break; + case DNS_R_UNCHANGED: + case ISC_R_NOTIMPLEMENTED: + /* + * Either the digest is not + * supported and we cannot + * construct the CDS for it, or + * the CDS with this digest is + * not present in the CDS RRset. + */ + break; + default: + goto cleanup; + } + } } if (dns_rdataset_isassociated(cdnskey)) { @@ -1936,6 +1963,7 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, keystr); delrdata(&cdnskeyrdata, diff, origin, cdnskey->ttl, mctx); + changed = true; } } } @@ -1944,7 +1972,10 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, if (!dns_rdataset_isassociated(cds) && !dns_rdataset_isassociated(cdnskey)) { - return ISC_R_SUCCESS; + if (changed) { + return ISC_R_SUCCESS; + } + return DNS_R_UNCHANGED; } /* @@ -1961,12 +1992,30 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, &cdnskeyrdata)); if (dns_rdataset_isassociated(cds)) { - delete_cds(key, &cdnskeyrdata, (const char *)keystr, - cds, DNS_DSDIGEST_SHA1, diff, mctx); - delete_cds(key, &cdnskeyrdata, (const char *)keystr, - cds, DNS_DSDIGEST_SHA256, diff, mctx); - delete_cds(key, &cdnskeyrdata, (const char *)keystr, - cds, DNS_DSDIGEST_SHA384, diff, mctx); + for (dns_dsdigest_t digest = DNS_DSDIGEST_SHA1; + digest < DNS_DSDIGEST_TOTAL; digest++) + { + result = delete_cds(key, &cdnskeyrdata, + (const char *)keystr, cds, + digest, diff, mctx); + switch (result) { + case ISC_R_SUCCESS: + changed = true; + break; + case DNS_R_UNCHANGED: + case ISC_R_NOTIMPLEMENTED: + /* + * Either the digest is not + * supported and we cannot + * construct the CDS for it, or + * the CDS with this digest is + * not present in the CDS RRset. + */ + break; + default: + goto cleanup; + } + } } if (dns_rdataset_isassociated(cdnskey)) { @@ -1978,11 +2027,15 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, keystr); delrdata(&cdnskeyrdata, diff, origin, cdnskey->ttl, mctx); + changed = true; } } } - result = ISC_R_SUCCESS; + if (changed) { + return ISC_R_SUCCESS; + } + return DNS_R_UNCHANGED; cleanup: return result; @@ -1999,6 +2052,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, dns_rdata_t cds_delete = DNS_RDATA_INIT; dns_rdata_t cdnskey_delete = DNS_RDATA_INIT; isc_region_t r; + bool changed = false; r.base = keybuf; r.length = sizeof(keybuf); @@ -2021,6 +2075,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, "published", namebuf); addrdata(&cds_delete, diff, origin, ttl, mctx); + changed = true; } } else { if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete)) @@ -2031,6 +2086,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, "deleted", namebuf); delrdata(&cds_delete, diff, origin, cds->ttl, mctx); + changed = true; } } @@ -2044,6 +2100,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, "published", namebuf); addrdata(&cdnskey_delete, diff, origin, ttl, mctx); + changed = true; } } else { if (dns_rdataset_isassociated(cdnskey) && @@ -2056,10 +2113,14 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, namebuf); delrdata(&cdnskey_delete, diff, origin, cdnskey->ttl, mctx); + changed = true; } } - return ISC_R_SUCCESS; + if (changed) { + return ISC_R_SUCCESS; + } + return DNS_R_UNCHANGED; } /* diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h index eed02b00da..14f9e468ae 100644 --- a/lib/dns/include/dns/dnssec.h +++ b/lib/dns/include/dns/dnssec.h @@ -403,6 +403,7 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, * * Returns: *\li ISC_R_SUCCESS + *\li DNS_R_UNCHANGED - if the CDS and CDNSKEY sets have not changed *\li Other values indicate error */ @@ -421,6 +422,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, * * Returns: *\li ISC_R_SUCCESS + *\li DNS_R_UNCHANGED - if the CDS and CDNSKEY sets have not changed *\li Other values indicate error */ diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h index 9f1e7938de..edad308ea1 100644 --- a/lib/dns/include/dns/ds.h +++ b/lib/dns/include/dns/ds.h @@ -31,6 +31,9 @@ #define DNS_DSDIGEST_SHA256PRIVATE (7) #define DNS_DSDIGEST_SHA384PRIVATE (8) #define DNS_DSDIGEST_SM3PRIVATE (9) +#define DNS_DSDIGEST_TOTAL (10) +#else +#define DNS_DSDIGEST_TOTAL (7) #endif #define DNS_DSDIGEST_MAX (255) diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 64658232af..0aeaed0b05 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -21624,7 +21624,10 @@ zone_rekey(dns_zone_t *zone) { result = dns_dnssec_syncupdate(&dnskeys, &rmkeys, &cdsset, &cdnskeyset, now, &digests, cdnskeypub, ttl, &diff, mctx); - if (result != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "zone_rekey:CDS/CDNSKEY updated"); + } else if (result != DNS_R_UNCHANGED) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY: %s", isc_result_totext(result)); @@ -21660,7 +21663,10 @@ zone_rekey(dns_zone_t *zone) { result = dns_dnssec_syncdelete( &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl, &diff, mctx, cdsdel, cdnskeydel); - if (result != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "zone_rekey:CDS/CDNSKEY updated (DELETE)"); + } else if (result != DNS_R_UNCHANGED) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY " "DELETE records: %s", From 8d83fbaf85ae9131e8cdb95ad385bcae8b263f43 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 28 Oct 2025 08:30:05 +0100 Subject: [PATCH 02/13] Maintain separate notify contexts for SOA and CDS With Generalized DNS Notifications, a zone may need to send different NOTIFY messages for different reasons. Introduce a method to initialize a notify context and maintain a notify contexts per RRtype. --- lib/dns/include/dns/notify.h | 12 +++++++ lib/dns/notify.c | 13 ++++++++ lib/dns/zone.c | 61 +++++++++++++++++------------------- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/lib/dns/include/dns/notify.h b/lib/dns/include/dns/notify.h index 11aaffc66a..02a518d498 100644 --- a/lib/dns/include/dns/notify.h +++ b/lib/dns/include/dns/notify.h @@ -29,6 +29,8 @@ struct dns_notifyctx { dns_acl_t *notify_acl; + dns_rdatatype_t type; + isc_sockaddr_t notifyfrom; dns_notifylist_t notifies; @@ -63,6 +65,16 @@ typedef enum dns_notify_flags { DNS_NOTIFY_TCP = 1 << 2, } dns_notify_flags_t; +void +dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type); +/*% + * Initializes a notify context for the RRtype 'type'. + * + * Requires: + * 'nctx' is not NULL. + * + */ + void dns_notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp); /*%< diff --git a/lib/dns/notify.c b/lib/dns/notify.c index da06276393..cdb0abcc1e 100644 --- a/lib/dns/notify.c +++ b/lib/dns/notify.c @@ -39,6 +39,19 @@ notify_log(dns_notify_t *notify, int level, const char *fmt, ...) { va_end(ap); } +void +dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type) { + dns_notifyctx_t ctx = { + .type = type, + .notifytype = dns_notifytype_yes, + .notifies = ISC_LIST_INITIALIZER, + }; + isc_sockaddr_any(&ctx.notifysrc4); + isc_sockaddr_any6(&ctx.notifysrc6); + + *nctx = ctx; +} + void dns_notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp) { dns_notify_t *notify; diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 0aeaed0b05..f555d0d9d2 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -340,7 +340,8 @@ struct dns_zone { uint32_t fetchcount[ZONEFETCHTYPE_COUNT]; dns_remote_t alsonotify; - dns_notifyctx_t notifyctx; + dns_notifyctx_t notifysoa; + dns_notifyctx_t notifycds; isc_sockaddr_t parentalsrc4; isc_sockaddr_t parentalsrc6; @@ -1085,13 +1086,6 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx, isc_tid_t tid) { dns_remote_t r = { .magic = DNS_REMOTE_MAGIC, }; - dns_notifyctx_t nc = { - .notifytype = dns_notifytype_yes, - .notifies = ISC_LIST_INITIALIZER, - }; - isc_sockaddr_any(&nc.notifysrc4); - isc_sockaddr_any6(&nc.notifysrc6); - zone->notifyctx = nc; isc_mem_attach(mctx, &zone->mctx); isc_mutex_init(&zone->lock); @@ -1111,6 +1105,9 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx, isc_tid_t tid) { zone->defaultkasp = NULL; ISC_LIST_INIT(zone->keyring); + dns_notifyctx_init(&zone->notifysoa, dns_rdatatype_soa); + dns_notifyctx_init(&zone->notifycds, dns_rdatatype_cds); + isc_stats_create(mctx, &zone->gluecachestats, dns_gluecachestatscounter_max); @@ -1251,8 +1248,8 @@ dns__zone_free(dns_zone_t *zone) { if (zone->forward_acl != NULL) { dns_acl_detach(&zone->forward_acl); } - if (zone->notifyctx.notify_acl != NULL) { - dns_acl_detach(&zone->notifyctx.notify_acl); + if (zone->notifysoa.notify_acl != NULL) { + dns_acl_detach(&zone->notifysoa.notify_acl); } if (zone->query_acl != NULL) { dns_acl_detach(&zone->query_acl); @@ -1370,7 +1367,7 @@ dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); - zone->notifyctx.notifytype = notifytype; + zone->notifysoa.notifytype = notifytype; UNLOCK_ZONE(zone); } @@ -6339,7 +6336,7 @@ dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { REQUIRE(notifysrc != NULL); LOCK_ZONE(zone); - zone->notifyctx.notifysrc4 = *notifysrc; + zone->notifysoa.notifysrc4 = *notifysrc; UNLOCK_ZONE(zone); } @@ -6349,7 +6346,7 @@ dns_zone_getnotifysrc4(dns_zone_t *zone, isc_sockaddr_t *notifysrc) { REQUIRE(notifysrc != NULL); LOCK_ZONE(zone); - *notifysrc = zone->notifyctx.notifysrc4; + *notifysrc = zone->notifysoa.notifysrc4; UNLOCK_ZONE(zone); } @@ -6359,7 +6356,7 @@ dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { REQUIRE(notifysrc != NULL); LOCK_ZONE(zone); - zone->notifyctx.notifysrc6 = *notifysrc; + zone->notifysoa.notifysrc6 = *notifysrc; UNLOCK_ZONE(zone); } @@ -6369,7 +6366,7 @@ dns_zone_getnotifysrc6(dns_zone_t *zone, isc_sockaddr_t *notifysrc) { REQUIRE(notifysrc != NULL); LOCK_ZONE(zone); - *notifysrc = zone->notifyctx.notifysrc6; + *notifysrc = zone->notifysoa.notifysrc6; UNLOCK_ZONE(zone); } @@ -12463,7 +12460,7 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { DNS_ZONEFLG_NEEDSTARTUPNOTIFY | DNS_ZONEFLG_NOTIFYNODEFER | DNS_ZONEFLG_NOTIFYDEFERRED); - notifytype = zone->notifyctx.notifytype; + notifytype = zone->notifysoa.notifytype; DNS_ZONE_TIME_ADD(now, zone->notifydelay, &zone->notifytime); UNLOCK_ZONE(zone); @@ -12582,7 +12579,7 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { goto next; } - if (dns_notify_isqueued(&zone->notifyctx, flags, NULL, &dst, + if (dns_notify_isqueued(&zone->notifysoa, flags, NULL, &dst, key, transport)) { if (key != NULL) { @@ -12612,7 +12609,7 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { transport = NULL; } - ISC_LIST_APPEND(zone->notifyctx.notifies, notify, link); + ISC_LIST_APPEND(zone->notifysoa.notifies, notify, link); result = dns_notify_queue(notify, startup); if (result != ISC_R_SUCCESS) { dns_notify_destroy(notify, true); @@ -12671,7 +12668,7 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { } LOCK_ZONE(zone); - isqueued = dns_notify_isqueued(&zone->notifyctx, flags, + isqueued = dns_notify_isqueued(&zone->notifysoa, flags, &ns.name, NULL, NULL, NULL); UNLOCK_ZONE(zone); if (isqueued) { @@ -12681,7 +12678,7 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { dns_zone_iattach(zone, ¬ify->zone); dns_name_dup(&ns.name, zone->mctx, ¬ify->ns); LOCK_ZONE(zone); - ISC_LIST_APPEND(zone->notifyctx.notifies, notify, link); + ISC_LIST_APPEND(zone->notifysoa.notifies, notify, link); UNLOCK_ZONE(zone); dns_notify_find_address(notify); } @@ -14609,7 +14606,7 @@ zone_shutdown(void *arg) { checkds_cancel(zone); - dns_notify_cancel(&zone->notifyctx); + dns_notify_cancel(&zone->notifysoa); forward_cancel(zone); @@ -15000,13 +14997,13 @@ dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, /* * Accept notify requests from non primaries if they are on - * 'zone->notifyctx.notify_acl'. + * 'zone->notifysoa.notify_acl'. */ tsigkey = dns_message_gettsigkey(msg); tsig = dns_tsigkey_identity(tsigkey); if (i >= dns_remote_count(&zone->primaries) && - zone->notifyctx.notify_acl != NULL && - (dns_acl_match(&netaddr, tsig, zone->notifyctx.notify_acl, + zone->notifysoa.notify_acl != NULL && + (dns_acl_match(&netaddr, tsig, zone->notifysoa.notify_acl, zone->view->aclenv, &match, NULL) == ISC_R_SUCCESS) && match > 0) @@ -15070,7 +15067,7 @@ dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, */ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); - zone->notifyctx.notifyfrom = *from; + zone->notifysoa.notifyfrom = *from; UNLOCK_ZONE(zone); if (have_serial) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, @@ -15096,7 +15093,7 @@ dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "notify from %s: no serial", fromtext); } - zone->notifyctx.notifyfrom = *from; + zone->notifysoa.notifyfrom = *from; UNLOCK_ZONE(zone); if (to != NULL) { @@ -15111,10 +15108,10 @@ dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); - if (zone->notifyctx.notify_acl != NULL) { - dns_acl_detach(&zone->notifyctx.notify_acl); + if (zone->notifysoa.notify_acl != NULL) { + dns_acl_detach(&zone->notifysoa.notify_acl); } - dns_acl_attach(acl, &zone->notifyctx.notify_acl); + dns_acl_attach(acl, &zone->notifysoa.notify_acl); UNLOCK_ZONE(zone); } @@ -15240,8 +15237,8 @@ dns_zone_clearnotifyacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); - if (zone->notifyctx.notify_acl != NULL) { - dns_acl_detach(&zone->notifyctx.notify_acl); + if (zone->notifysoa.notify_acl != NULL) { + dns_acl_detach(&zone->notifysoa.notify_acl); } UNLOCK_ZONE(zone); } @@ -15666,7 +15663,7 @@ dns_notifyctx_t * dns__zone_getnotifyctx(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); - return &zone->notifyctx; + return &zone->notifysoa; } void From 6554a5f9f78f6c5b65d3421b7834d2756863c6ee Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 28 Oct 2025 10:59:04 +0100 Subject: [PATCH 03/13] Add new 'notify-cds' configuration option Add a new configuration option to enable/disable sending NOTIFY(CDS) messages. --- bin/include/defaultconfig.h | 1 + bin/named/server.c | 14 +++++--- bin/named/zoneconf.c | 20 +++++++++-- .../system/checkconf/bad-notifycdstype.conf | 22 +++++++++++++ bin/tests/system/checkconf/good.conf.j2 | 2 ++ doc/misc/mirror.zoneopt | 1 + doc/misc/options | 3 ++ doc/misc/primary.zoneopt | 1 + doc/misc/secondary.zoneopt | 1 + lib/dns/include/dns/zone.h | 5 +-- lib/dns/zone.c | 33 ++++++++++++++++--- lib/isccfg/namedconf.c | 2 ++ 12 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 bin/tests/system/checkconf/bad-notifycdstype.conf diff --git a/bin/include/defaultconfig.h b/bin/include/defaultconfig.h index 8e24757260..9ecd96588e 100644 --- a/bin/include/defaultconfig.h +++ b/bin/include/defaultconfig.h @@ -215,6 +215,7 @@ options {\n\ min-transfer-rate-in 10240 5;\n\ multi-master no;\n\ notify yes;\n\ + notify-cds no;\n\ notify-defer 0;\n\ notify-delay 5;\n\ notify-to-soa no;\n\ diff --git a/bin/named/server.c b/bin/named/server.c index 1cabf96e72..7e43ef6c11 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -1845,7 +1845,8 @@ dns64_reverse(dns_view_t *view, isc_mem_t *mctx, isc_netaddr_t *na, dns_zone_setqueryonacl(zone, view->queryonacl); } dns_zone_setcheckdstype(zone, dns_checkdstype_no); - dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_soa, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_cds, dns_notifytype_no); dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); setquerystats(zone, mctx, dns_zonestat_none); CHECK(dns_view_addzone(view, zone)); @@ -3220,7 +3221,8 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view, dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION, false); dns_zone_setcheckdstype(zone, dns_checkdstype_no); - dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_soa, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_cds, dns_notifytype_no); dns_zone_setautomatic(zone, true); if (view->queryacl != NULL) { dns_zone_setqueryacl(zone, view->queryacl); @@ -3319,7 +3321,10 @@ create_ipv4only_zone(dns_zone_t *pzone, dns_view_t *view, dns_zone_setstats(zone, named_g_server->zonestats); dns_zone_setdbtype(zone, dbtypec, dbtype); dns_zone_setcheckdstype(zone, dns_checkdstype_no); - dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_soa, + dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_cds, + dns_notifytype_no); dns_zone_setautomatic(zone, true); dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); } else { @@ -6394,7 +6399,8 @@ add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx) { dns_acl_detach(&none); dns_zone_setcheckdstype(zone, dns_checkdstype_no); - dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_soa, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_cds, dns_notifytype_no); dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); dns_zone_setjournalsize(zone, 0); diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 1d9ab722cf..1af25b8adc 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1209,9 +1209,20 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, notifytype = process_notifytype(notifytype, ztype, zname, nodefault); if (raw != NULL) { - dns_zone_setnotifytype(raw, dns_notifytype_no); + dns_zone_setnotifytype(raw, dns_rdatatype_soa, + dns_notifytype_no); } - dns_zone_setnotifytype(zone, notifytype); + dns_zone_setnotifytype(zone, dns_rdatatype_soa, notifytype); + + obj = NULL; + result = named_config_get(maps, "notify-cds", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (raw != NULL) { + dns_zone_setnotifytype(raw, dns_rdatatype_cds, + dns_notifytype_no); + } + dns_zone_setnotifytype(zone, dns_rdatatype_cds, + cfg_obj_asboolean(obj)); obj = NULL; result = named_config_get(maps, "also-notify", &obj); @@ -1473,7 +1484,10 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } } } else if (ztype == dns_zone_redirect) { - dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_soa, + dns_notifytype_no); + dns_zone_setnotifytype(zone, dns_rdatatype_cds, + dns_notifytype_no); obj = NULL; result = named_config_get(maps, "max-journal-size", &obj); diff --git a/bin/tests/system/checkconf/bad-notifycdstype.conf b/bin/tests/system/checkconf/bad-notifycdstype.conf new file mode 100644 index 0000000000..6dc6ff9678 --- /dev/null +++ b/bin/tests/system/checkconf/bad-notifycdstype.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Bad notify-cds type + */ + +zone dummy { + type primary; + file "xxxx"; + notify-cds explicit; +}; diff --git a/bin/tests/system/checkconf/good.conf.j2 b/bin/tests/system/checkconf/good.conf.j2 index fc33cb6503..2f98e67bf9 100644 --- a/bin/tests/system/checkconf/good.conf.j2 +++ b/bin/tests/system/checkconf/good.conf.j2 @@ -194,6 +194,7 @@ view "fourth" { 1.2.3.5; }; dnssec-policy "test"; + notify-cds no; parental-source 10.10.10.10; }; zone "dnssec-default" { @@ -203,6 +204,7 @@ view "fourth" { "parents"; }; dnssec-policy "default"; + notify-cds yes; }; zone "dnssec-inherit" { type primary; diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index 346f093d33..2cdf57f2af 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -29,6 +29,7 @@ zone [ ] { min-transfer-rate-in ; multi-master ; notify ( explicit | master-only | primary-only | ); + notify-cds ; notify-defer ; notify-delay ; notify-source ( | * ); diff --git a/doc/misc/options b/doc/misc/options index 39b7d2f4a9..2f1f03ec98 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -210,6 +210,7 @@ options { no-case-compress { ; ... }; nocookie-udp-size ; notify ( explicit | master-only | primary-only | ); + notify-cds ; notify-defer ; notify-delay ; notify-rate ; @@ -415,6 +416,7 @@ template { min-transfer-rate-in ; multi-master ; notify ( explicit | master-only | primary-only | ); + notify-cds ; notify-defer ; notify-delay ; notify-source ( | * ); @@ -586,6 +588,7 @@ view [ ] { no-case-compress { ; ... }; nocookie-udp-size ; notify ( explicit | master-only | primary-only | ); + notify-cds ; notify-defer ; notify-delay ; notify-source ( | * ); diff --git a/doc/misc/primary.zoneopt b/doc/misc/primary.zoneopt index dfebff879b..a5e26bee89 100644 --- a/doc/misc/primary.zoneopt +++ b/doc/misc/primary.zoneopt @@ -44,6 +44,7 @@ zone [ ] { max-types-per-name ; max-zone-ttl ( unlimited | ); // deprecated notify ( explicit | master-only | primary-only | ); + notify-cds ; notify-defer ; notify-delay ; notify-source ( | * ); diff --git a/doc/misc/secondary.zoneopt b/doc/misc/secondary.zoneopt index cbbd5fea52..cc1d28a984 100644 --- a/doc/misc/secondary.zoneopt +++ b/doc/misc/secondary.zoneopt @@ -41,6 +41,7 @@ zone [ ] { min-transfer-rate-in ; multi-master ; notify ( explicit | master-only | primary-only | ); + notify-cds ; notify-defer ; notify-delay ; notify-source ( | * ); diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 04aa03dd92..ef95a57243 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -1541,9 +1541,10 @@ dns_zone_getrequesttransporttype(dns_zone_t *zone); */ void -dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype); +dns_zone_setnotifytype(dns_zone_t *zone, dns_rdatatype_t type, + dns_notifytype_t notifytype); /*%< - * Sets zone notify method to "notifytype" + * Sets zone notify(type) method to "notifytype" */ void diff --git a/lib/dns/zone.c b/lib/dns/zone.c index f555d0d9d2..bc8873ee69 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -1251,6 +1251,9 @@ dns__zone_free(dns_zone_t *zone) { if (zone->notifysoa.notify_acl != NULL) { dns_acl_detach(&zone->notifysoa.notify_acl); } + if (zone->notifycds.notify_acl != NULL) { + dns_acl_detach(&zone->notifycds.notify_acl); + } if (zone->query_acl != NULL) { dns_acl_detach(&zone->query_acl); } @@ -1363,11 +1366,23 @@ dns_zone_getclass(dns_zone_t *zone) { } void -dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype) { +dns_zone_setnotifytype(dns_zone_t *zone, dns_rdatatype_t type, + dns_notifytype_t notifytype) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); - zone->notifysoa.notifytype = notifytype; + switch (type) { + case dns_rdatatype_soa: + zone->notifysoa.notifytype = notifytype; + break; + case dns_rdatatype_cds: + INSIST(notifytype == dns_notifytype_no || + notifytype == dns_notifytype_yes); + zone->notifycds.notifytype = notifytype; + break; + default: + UNREACHABLE(); + } UNLOCK_ZONE(zone); } @@ -6337,6 +6352,7 @@ dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { LOCK_ZONE(zone); zone->notifysoa.notifysrc4 = *notifysrc; + zone->notifycds.notifysrc4 = *notifysrc; UNLOCK_ZONE(zone); } @@ -6347,6 +6363,7 @@ dns_zone_getnotifysrc4(dns_zone_t *zone, isc_sockaddr_t *notifysrc) { LOCK_ZONE(zone); *notifysrc = zone->notifysoa.notifysrc4; + *notifysrc = zone->notifycds.notifysrc4; UNLOCK_ZONE(zone); } @@ -6357,6 +6374,7 @@ dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { LOCK_ZONE(zone); zone->notifysoa.notifysrc6 = *notifysrc; + zone->notifycds.notifysrc6 = *notifysrc; UNLOCK_ZONE(zone); } @@ -6367,6 +6385,7 @@ dns_zone_getnotifysrc6(dns_zone_t *zone, isc_sockaddr_t *notifysrc) { LOCK_ZONE(zone); *notifysrc = zone->notifysoa.notifysrc6; + *notifysrc = zone->notifycds.notifysrc6; UNLOCK_ZONE(zone); } @@ -14607,6 +14626,7 @@ zone_shutdown(void *arg) { checkds_cancel(zone); dns_notify_cancel(&zone->notifysoa); + dns_notify_cancel(&zone->notifycds); forward_cancel(zone); @@ -15107,11 +15127,11 @@ void dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); + dns_zone_clearnotifyacl(zone); + LOCK_ZONE(zone); - if (zone->notifysoa.notify_acl != NULL) { - dns_acl_detach(&zone->notifysoa.notify_acl); - } dns_acl_attach(acl, &zone->notifysoa.notify_acl); + dns_acl_attach(acl, &zone->notifycds.notify_acl); UNLOCK_ZONE(zone); } @@ -15240,6 +15260,9 @@ dns_zone_clearnotifyacl(dns_zone_t *zone) { if (zone->notifysoa.notify_acl != NULL) { dns_acl_detach(&zone->notifysoa.notify_acl); } + if (zone->notifycds.notify_acl != NULL) { + dns_acl_detach(&zone->notifycds.notify_acl); + } UNLOCK_ZONE(zone); } diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 90fc49a74e..f50c20d31e 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2685,6 +2685,8 @@ static cfg_clausedef_t zone_clauses[] = { CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB, NULL }, { "notify", &cfg_type_notifytype, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR, NULL }, + { "notify-cds", &cfg_type_boolean, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR, NULL }, { "notify-defer", &cfg_type_uint32, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR, NULL }, { "notify-delay", &cfg_type_uint32, From dda2e99c36a8b52f9d82aae806a1a82df3ec7753 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 28 Oct 2025 11:37:47 +0100 Subject: [PATCH 04/13] Document 'notify-cds' configuration option Add text about the 'notify-cds' option in the ARM reference. --- doc/arm/reference.rst | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index bc680650c6..e238195748 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -2097,9 +2097,9 @@ Boolean Options .. namedconf:statement:: notify :tags: transfer - :short: Controls whether ``NOTIFY`` messages are sent on zone changes. + :short: Controls whether ``NOTIFY(SOA)`` messages are sent on zone changes. - If set to ``yes`` (the default), DNS NOTIFY messages are sent when a + If set to ``yes`` (the default), DNS NOTIFY(SOA) messages are sent when a zone the server is authoritative for changes; see :ref:`using notify`. The messages are sent to the servers listed in the zone's NS records (except the primary server identified in the SOA MNAME field), and to @@ -2115,6 +2115,22 @@ Boolean Options statement. It would only be necessary to turn off this option if it caused secondary zones to crash. +.. namedconf:statement:: notify-cds + :tags: dnssec + :short: Controls whether ``NOTIFY(CDS)`` messages are sent on zone changes. + + If set to ``yes``, DNS NOTIFY(CDS) messages are sent when the CDS or CDNSKEY + RRset changes. The messages are sent to the servers listed in the parent + zone's matching DSYNC records. A DSYNC record matches if the owner name under + `_dsync` subdomain of the parent zone corresponds to the given zone. For + example, the zone `child.example` should have a DSYNC record at + `child._dsync.example`. In addition, the RRtype field of the record must be + `CDS` and the Scheme field must be 1 (NOTIFY). + + The default is ``no``. The :namedconf:ref:`notify-cds` option may also be + specified in the :any:`zone` statement, in which case it overrides the + ``options notify-cds`` statement. + .. namedconf:statement:: notify-to-soa :tags: transfer :short: Controls whether the name servers in the NS RRset are checked against the SOA MNAME. From 121d372236035f474fb0e6098a355060cd395014 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 28 Oct 2025 15:25:29 +0100 Subject: [PATCH 05/13] Add port parameter to dns_notify_create() The DSYNC record has a Port rdata field, so NOTIFY(CDS) messages may be configured at different ports. When creating a new notify, allow for specifying the port. --- lib/dns/include/dns/notify.h | 14 ++++++++------ lib/dns/notify.c | 20 ++++++++++++-------- lib/dns/zone.c | 13 ++++++++----- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/dns/include/dns/notify.h b/lib/dns/include/dns/notify.h index 02a518d498..d6c64e5a02 100644 --- a/lib/dns/include/dns/notify.h +++ b/lib/dns/include/dns/notify.h @@ -51,6 +51,7 @@ struct dns_notify { dns_adbfind_t *find; dns_request_t *request; dns_name_t ns; + in_port_t port; isc_sockaddr_t src; isc_sockaddr_t dst; dns_tsigkey_t *key; @@ -76,7 +77,8 @@ dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type); */ void -dns_notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp); +dns_notify_create(isc_mem_t *mctx, in_port_t port, unsigned int flags, + dns_notify_t **notifyp); /*%< * Create a notify structure to maintain state. * @@ -96,14 +98,14 @@ dns_notify_destroy(dns_notify_t *notify, bool zone_locked); */ bool -dns_notify_isqueued(dns_notifyctx_t *nctx, unsigned int flags, dns_name_t *name, - isc_sockaddr_t *addr, dns_tsigkey_t *key, +dns_notify_isqueued(dns_notifyctx_t *nctx, in_port_t port, unsigned int flags, + dns_name_t *name, isc_sockaddr_t *addr, dns_tsigkey_t *key, dns_transport_t *transport); /*%< * Check if we already have a notify queued matching name, destination - * address, TSIG key, and transport. Will requeue on the normal notify - * ratelimiter if the notify was enqueued on the startup ratelimiter and - * this is not a startup notify. + * address and port, TSIG key, and transport. Will requeue on the normal + * notify ratelimiter if the notify was enqueued on the startup ratelimiter + * and this is not a startup notify. * * Requires: * 'nctx' is not NULL diff --git a/lib/dns/notify.c b/lib/dns/notify.c index cdb0abcc1e..50f0bb6cea 100644 --- a/lib/dns/notify.c +++ b/lib/dns/notify.c @@ -53,7 +53,8 @@ dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type) { } void -dns_notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp) { +dns_notify_create(isc_mem_t *mctx, in_port_t port, unsigned int flags, + dns_notify_t **notifyp) { dns_notify_t *notify; REQUIRE(notifyp != NULL && *notifyp == NULL); @@ -61,6 +62,7 @@ dns_notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp) { notify = isc_mem_get(mctx, sizeof(*notify)); *notify = (dns_notify_t){ .flags = flags, + .port = port, }; isc_mem_attach(mctx, ¬ify->mctx); @@ -525,8 +527,8 @@ dns_notify_queue(dns_notify_t *notify, bool startup) { } bool -dns_notify_isqueued(dns_notifyctx_t *nctx, unsigned int flags, dns_name_t *name, - isc_sockaddr_t *addr, dns_tsigkey_t *key, +dns_notify_isqueued(dns_notifyctx_t *nctx, in_port_t port, unsigned int flags, + dns_name_t *name, isc_sockaddr_t *addr, dns_tsigkey_t *key, dns_transport_t *transport) { dns_notify_t *notify = NULL; isc_result_t result; @@ -540,7 +542,8 @@ dns_notify_isqueued(dns_notifyctx_t *nctx, unsigned int flags, dns_name_t *name, if ((name != NULL && dns_name_dynamic(&n->ns) && dns_name_equal(name, &n->ns)) || (addr != NULL && isc_sockaddr_equal(addr, &n->dst) && - n->key == key && n->transport == transport)) + n->port == port && n->key == key && + n->transport == transport)) { notify = n; goto requeue; @@ -646,8 +649,8 @@ notify_send(dns_notify_t *notify) { ISC_LIST_FOREACH(notify->find->list, ai, publink) { dst = ai->sockaddr; - if (dns_notify_isqueued(notifyctx, notify->flags, NULL, &dst, - NULL, NULL)) + if (dns_notify_isqueued(notifyctx, notify->port, notify->flags, + NULL, &dst, NULL, NULL)) { continue; } @@ -656,7 +659,8 @@ notify_send(dns_notify_t *notify) { } newnotify = NULL; flags = notify->flags & DNS_NOTIFY_NOSOA; - dns_notify_create(notify->mctx, flags, &newnotify); + dns_notify_create(notify->mctx, notify->port, flags, + &newnotify); dns__zone_iattach_locked(notify->zone, &newnotify->zone); ISC_LIST_APPEND(notifyctx->notifies, newnotify, link); newnotify->dst = dst; @@ -731,7 +735,7 @@ dns_notify_find_address(dns_notify_t *notify) { } result = dns_adb_createfind(adb, loop, process_notify_adb_event, notify, - ¬ify->ns, options, 0, view->dstport, 0, + ¬ify->ns, options, 0, notify->port, 0, NULL, NULL, NULL, ¬ify->find); dns_adb_detach(&adb); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index bc8873ee69..cceb56d2c7 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -12598,8 +12598,8 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { goto next; } - if (dns_notify_isqueued(&zone->notifysoa, flags, NULL, &dst, - key, transport)) + if (dns_notify_isqueued(&zone->notifysoa, zone->view->dstport, + flags, NULL, &dst, key, transport)) { if (key != NULL) { dns_tsigkey_detach(&key); @@ -12610,7 +12610,8 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { goto next; } - dns_notify_create(zone->mctx, flags, ¬ify); + dns_notify_create(zone->mctx, zone->view->dstport, flags, + ¬ify); zone_iattach(zone, ¬ify->zone); notify->src = src; notify->dst = dst; @@ -12687,13 +12688,15 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { } LOCK_ZONE(zone); - isqueued = dns_notify_isqueued(&zone->notifysoa, flags, + isqueued = dns_notify_isqueued(&zone->notifysoa, + zone->view->dstport, flags, &ns.name, NULL, NULL, NULL); UNLOCK_ZONE(zone); if (isqueued) { continue; } - dns_notify_create(zone->mctx, flags, ¬ify); + dns_notify_create(zone->mctx, zone->view->dstport, flags, + ¬ify); dns_zone_iattach(zone, ¬ify->zone); dns_name_dup(&ns.name, zone->mctx, ¬ify->ns); LOCK_ZONE(zone); From dc0437e5185156ecae0847a1f6f6eb6a7f27cb39 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 18 Nov 2025 09:56:34 +0100 Subject: [PATCH 06/13] Add type parameter to dns_notify_create() With Generalized DNS Notifications, a zone may need to send different type of NOTIFY messages for different reasons. When creating a new notify, allow for specifying the type. --- lib/dns/include/dns/notify.h | 20 +++++++++++--------- lib/dns/notify.c | 22 +++++++++++++--------- lib/dns/zone.c | 19 ++++++++++--------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/lib/dns/include/dns/notify.h b/lib/dns/include/dns/notify.h index d6c64e5a02..6755c2831a 100644 --- a/lib/dns/include/dns/notify.h +++ b/lib/dns/include/dns/notify.h @@ -51,6 +51,7 @@ struct dns_notify { dns_adbfind_t *find; dns_request_t *request; dns_name_t ns; + dns_rdatatype_t type; in_port_t port; isc_sockaddr_t src; isc_sockaddr_t dst; @@ -77,8 +78,8 @@ dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type); */ void -dns_notify_create(isc_mem_t *mctx, in_port_t port, unsigned int flags, - dns_notify_t **notifyp); +dns_notify_create(isc_mem_t *mctx, dns_rdatatype_t type, in_port_t port, + unsigned int flags, dns_notify_t **notifyp); /*%< * Create a notify structure to maintain state. * @@ -98,14 +99,15 @@ dns_notify_destroy(dns_notify_t *notify, bool zone_locked); */ bool -dns_notify_isqueued(dns_notifyctx_t *nctx, in_port_t port, unsigned int flags, - dns_name_t *name, isc_sockaddr_t *addr, dns_tsigkey_t *key, - dns_transport_t *transport); +dns_notify_isqueued(dns_notifyctx_t *nctx, dns_rdatatype_t type, in_port_t port, + unsigned int flags, dns_name_t *name, isc_sockaddr_t *addr, + dns_tsigkey_t *key, dns_transport_t *transport); /*%< - * Check if we already have a notify queued matching name, destination - * address and port, TSIG key, and transport. Will requeue on the normal - * notify ratelimiter if the notify was enqueued on the startup ratelimiter - * and this is not a startup notify. + * Check if we already have a notify queued matching name, type, + * destination address and port, TSIG key, and transport. Will + * requeue on the normal notify ratelimiter if the notify was + * enqueued on the startup ratelimiter and this is not a startup + * notify. * * Requires: * 'nctx' is not NULL diff --git a/lib/dns/notify.c b/lib/dns/notify.c index 50f0bb6cea..07b71b7242 100644 --- a/lib/dns/notify.c +++ b/lib/dns/notify.c @@ -53,8 +53,8 @@ dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type) { } void -dns_notify_create(isc_mem_t *mctx, in_port_t port, unsigned int flags, - dns_notify_t **notifyp) { +dns_notify_create(isc_mem_t *mctx, dns_rdatatype_t type, in_port_t port, + unsigned int flags, dns_notify_t **notifyp) { dns_notify_t *notify; REQUIRE(notifyp != NULL && *notifyp == NULL); @@ -63,6 +63,7 @@ dns_notify_create(isc_mem_t *mctx, in_port_t port, unsigned int flags, *notify = (dns_notify_t){ .flags = flags, .port = port, + .type = type, }; isc_mem_attach(mctx, ¬ify->mctx); @@ -527,9 +528,9 @@ dns_notify_queue(dns_notify_t *notify, bool startup) { } bool -dns_notify_isqueued(dns_notifyctx_t *nctx, in_port_t port, unsigned int flags, - dns_name_t *name, isc_sockaddr_t *addr, dns_tsigkey_t *key, - dns_transport_t *transport) { +dns_notify_isqueued(dns_notifyctx_t *nctx, dns_rdatatype_t type, in_port_t port, + unsigned int flags, dns_name_t *name, isc_sockaddr_t *addr, + dns_tsigkey_t *key, dns_transport_t *transport) { dns_notify_t *notify = NULL; isc_result_t result; @@ -539,6 +540,9 @@ dns_notify_isqueued(dns_notifyctx_t *nctx, in_port_t port, unsigned int flags, if (n->request != NULL) { continue; } + if (n->type != type) { + continue; + } if ((name != NULL && dns_name_dynamic(&n->ns) && dns_name_equal(name, &n->ns)) || (addr != NULL && isc_sockaddr_equal(addr, &n->dst) && @@ -649,8 +653,8 @@ notify_send(dns_notify_t *notify) { ISC_LIST_FOREACH(notify->find->list, ai, publink) { dst = ai->sockaddr; - if (dns_notify_isqueued(notifyctx, notify->port, notify->flags, - NULL, &dst, NULL, NULL)) + if (dns_notify_isqueued(notifyctx, notify->type, notify->port, + notify->flags, NULL, &dst, NULL, NULL)) { continue; } @@ -659,8 +663,8 @@ notify_send(dns_notify_t *notify) { } newnotify = NULL; flags = notify->flags & DNS_NOTIFY_NOSOA; - dns_notify_create(notify->mctx, notify->port, flags, - &newnotify); + dns_notify_create(notify->mctx, notify->type, notify->port, + flags, &newnotify); dns__zone_iattach_locked(notify->zone, &newnotify->zone); ISC_LIST_APPEND(notifyctx->notifies, newnotify, link); newnotify->dst = dst; diff --git a/lib/dns/zone.c b/lib/dns/zone.c index cceb56d2c7..db5f59b2b3 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -12598,8 +12598,9 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { goto next; } - if (dns_notify_isqueued(&zone->notifysoa, zone->view->dstport, - flags, NULL, &dst, key, transport)) + if (dns_notify_isqueued(&zone->notifysoa, dns_rdatatype_soa, + zone->view->dstport, flags, NULL, &dst, + key, transport)) { if (key != NULL) { dns_tsigkey_detach(&key); @@ -12610,8 +12611,8 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { goto next; } - dns_notify_create(zone->mctx, zone->view->dstport, flags, - ¬ify); + dns_notify_create(zone->mctx, dns_rdatatype_soa, + zone->view->dstport, flags, ¬ify); zone_iattach(zone, ¬ify->zone); notify->src = src; notify->dst = dst; @@ -12688,15 +12689,15 @@ zone_notify(dns_zone_t *zone, isc_time_t *now) { } LOCK_ZONE(zone); - isqueued = dns_notify_isqueued(&zone->notifysoa, - zone->view->dstport, flags, - &ns.name, NULL, NULL, NULL); + isqueued = dns_notify_isqueued( + &zone->notifysoa, dns_rdatatype_soa, + zone->view->dstport, flags, &ns.name, NULL, NULL, NULL); UNLOCK_ZONE(zone); if (isqueued) { continue; } - dns_notify_create(zone->mctx, zone->view->dstport, flags, - ¬ify); + dns_notify_create(zone->mctx, dns_rdatatype_soa, + zone->view->dstport, flags, ¬ify); dns_zone_iattach(zone, ¬ify->zone); dns_name_dup(&ns.name, zone->mctx, ¬ify->ns); LOCK_ZONE(zone); From 97b245c24c9c3f4992290800cbf5869119b2a0bb Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 25 Nov 2025 09:12:58 +0100 Subject: [PATCH 07/13] Use notify type in logging and for getting context Add the notify type to the log messages for clarity, and use it to retrieve the right notify context. --- lib/dns/notify.c | 62 ++++++++++++++++++++++++++++-------------------- lib/dns/zone.c | 12 ++++++++-- lib/dns/zone_p.h | 2 +- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/lib/dns/notify.c b/lib/dns/notify.c index 07b71b7242..98619c1115 100644 --- a/lib/dns/notify.c +++ b/lib/dns/notify.c @@ -87,7 +87,7 @@ dns_notify_destroy(dns_notify_t *notify, bool locked) { dns__zone_lock(notify->zone); } REQUIRE(dns__zone_locked(notify->zone)); - nctx = dns__zone_getnotifyctx(notify->zone); + nctx = dns__zone_getnotifyctx(notify->zone, notify->type); if (ISC_LINK_LINKED(notify, link)) { ISC_LIST_UNLINK(nctx->notifies, notify, link); } @@ -129,11 +129,15 @@ notify_done(void *arg) { isc_buffer_t buf; char rcode[128]; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; REQUIRE(DNS_NOTIFY_VALID(notify)); isc_buffer_init(&buf, rcode, sizeof(rcode)); isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); + + dns_rdatatype_format(notify->type, typebuf, sizeof(typebuf)); + /* WMM: This is changing the mctx from zone to notify. */ dns_message_create(notify->mctx, NULL, NULL, DNS_MESSAGE_INTENTPARSE, &message); @@ -152,32 +156,33 @@ notify_done(void *arg) { result = dns_rcode_totext(message->rcode, &buf); if (result == ISC_R_SUCCESS) { notify_log(notify, ISC_LOG_DEBUG(3), - "notify response from %s: %.*s", addrbuf, - (int)buf.used, rcode); + "notify(%s) response from %s: %.*s", typebuf, + addrbuf, (int)buf.used, rcode); } fail: dns_message_detach(&message); if (result == ISC_R_SUCCESS) { - notify_log(notify, ISC_LOG_DEBUG(1), "notify to %s successful", - addrbuf); + notify_log(notify, ISC_LOG_DEBUG(1), + "notify(%s) to %s successful", typebuf, addrbuf); } else if (result == ISC_R_SHUTTINGDOWN || result == ISC_R_CANCELED) { /* just destroy the notify */ } else if ((notify->flags & DNS_NOTIFY_TCP) == 0) { notify_log(notify, ISC_LOG_NOTICE, - "notify to %s failed: %s: retrying over TCP", - addrbuf, isc_result_totext(result)); + "notify(%s) to %s failed: %s: retrying over TCP", + typebuf, addrbuf, isc_result_totext(result)); notify->flags |= DNS_NOTIFY_TCP; dns_request_destroy(¬ify->request); dns_notify_queue(notify, notify->flags & DNS_NOTIFY_STARTUP); return; } else if (result == ISC_R_TIMEDOUT) { notify_log(notify, ISC_LOG_WARNING, - "notify to %s failed: %s: retries exceeded", addrbuf, - isc_result_totext(result)); + "notify(%s) to %s failed: %s: retries exceeded", + typebuf, addrbuf, isc_result_totext(result)); } else { - notify_log(notify, ISC_LOG_WARNING, "notify to %s failed: %s", - addrbuf, isc_result_totext(result)); + notify_log(notify, ISC_LOG_WARNING, + "notify(%s) to %s failed: %s", typebuf, addrbuf, + isc_result_totext(result)); } dns_notify_destroy(notify, false); } @@ -324,6 +329,7 @@ notify_send_toaddr(void *arg) { isc_netaddr_t dstip; dns_tsigkey_t *key = NULL; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; isc_sockaddr_t src; unsigned int options; bool have_notifysource = false; @@ -333,13 +339,16 @@ notify_send_toaddr(void *arg) { dns__zone_lock(notify->zone); - notifyctx = dns__zone_getnotifyctx(notify->zone); + notifyctx = dns__zone_getnotifyctx(notify->zone, notify->type); zmgr = dns_zone_getmgr(notify->zone); view = dns_zone_getview(notify->zone); loop = dns_zone_getloop(notify->zone); result = dns_zone_getdb(notify->zone, &zonedb); + isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); + dns_rdatatype_format(notify->type, typebuf, sizeof(typebuf)); + if (!dns__zone_loaded(notify->zone) || notify->rlevent->canceled || dns__zone_exiting(notify->zone) || zmgr == NULL || view == NULL || view->requestmgr == NULL || loop == NULL || zonedb == NULL || @@ -357,8 +366,8 @@ notify_send_toaddr(void *arg) { IN6_IS_ADDR_V4MAPPED(¬ify->dst.type.sin6.sin6_addr)) { notify_log(notify, ISC_LOG_DEBUG(3), - "notify: ignoring IPv6 mapped IPV4 address: %s", - addrbuf); + "notify(%s): ignoring IPv6 mapped IPV4 address: %s", + typebuf, addrbuf); result = ISC_R_CANCELED; goto cleanup; } @@ -374,9 +383,9 @@ notify_send_toaddr(void *arg) { result = dns_view_getpeertsig(view, &dstip, &key); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { notify_log(notify, ISC_LOG_ERROR, - "NOTIFY to %s not sent. " + "NOTIFY(%s) to %s not sent. " "Peer TSIG key lookup failure.", - addrbuf); + typebuf, addrbuf); goto cleanup_message; } } @@ -386,11 +395,11 @@ notify_send_toaddr(void *arg) { dns_name_format(key->name, namebuf, sizeof(namebuf)); notify_log(notify, ISC_LOG_INFO, - "sending notify to %s : TSIG (%s)", addrbuf, - namebuf); + "sending notify(%s) to %s : TSIG (%s)", typebuf, + addrbuf, namebuf); } else { - notify_log(notify, ISC_LOG_INFO, "sending notify to %s", - addrbuf); + notify_log(notify, ISC_LOG_INFO, "sending notify(%s) to %s", + typebuf, addrbuf); } options = 0; if (view->peers != NULL) { @@ -466,8 +475,8 @@ again: goto cleanup_key; } else if ((notify->flags & DNS_NOTIFY_TCP) == 0) { notify_log(notify, ISC_LOG_NOTICE, - "notify to %s failed: %s: retrying over TCP", - addrbuf, isc_result_totext(result)); + "notify(%s) to %s failed: %s: retrying over TCP", + typebuf, addrbuf, isc_result_totext(result)); notify->flags |= DNS_NOTIFY_TCP; goto again; } @@ -491,8 +500,9 @@ cleanup: if (result != ISC_R_SUCCESS) { isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); - notify_log(notify, ISC_LOG_WARNING, "notify to %s failed: %s", - addrbuf, isc_result_totext(result)); + notify_log(notify, ISC_LOG_WARNING, + "notify(%s) to %s failed: %s", typebuf, addrbuf, + isc_result_totext(result)); dns_notify_destroy(notify, false); } } @@ -591,7 +601,7 @@ notify_isself(dns_notify_t *notify, isc_sockaddr_t *dst) { dns_isselffunc_t isselffunc; void *isselfarg = NULL; - notifyctx = dns__zone_getnotifyctx(notify->zone); + notifyctx = dns__zone_getnotifyctx(notify->zone, notify->type); view = dns_zone_getview(notify->zone); dns__zone_getisself(notify->zone, &isselffunc, &isselfarg); if (view == NULL || isselffunc == NULL) { @@ -649,7 +659,7 @@ notify_send(dns_notify_t *notify) { if (dns__zone_exiting(notify->zone)) { return; } - notifyctx = dns__zone_getnotifyctx(notify->zone); + notifyctx = dns__zone_getnotifyctx(notify->zone, notify->type); ISC_LIST_FOREACH(notify->find->list, ai, publink) { dst = ai->sockaddr; diff --git a/lib/dns/zone.c b/lib/dns/zone.c index db5f59b2b3..ee632cef0a 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -15687,10 +15687,18 @@ dns_zone_getrdclass(dns_zone_t *zone) { } dns_notifyctx_t * -dns__zone_getnotifyctx(dns_zone_t *zone) { +dns__zone_getnotifyctx(dns_zone_t *zone, dns_rdatatype_t type) { REQUIRE(DNS_ZONE_VALID(zone)); - return &zone->notifysoa; + switch (type) { + case dns_rdatatype_soa: + return &zone->notifysoa; + case dns_rdatatype_cds: + return &zone->notifycds; + default: + UNREACHABLE(); + } + return NULL; } void diff --git a/lib/dns/zone_p.h b/lib/dns/zone_p.h index 9a7d3481fa..158ee0d8a8 100644 --- a/lib/dns/zone_p.h +++ b/lib/dns/zone_p.h @@ -108,7 +108,7 @@ dns__zone_stats_increment(dns_zone_t *zone, isc_statscounter_t counter); */ dns_notifyctx_t * -dns__zone_getnotifyctx(dns_zone_t *zone); +dns__zone_getnotifyctx(dns_zone_t *zone, dns_rdatatype_t type); /*%< * Returns the notify context. * From 9fa0493a94390900194ffefb12339a8958e5b17d Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 1 Dec 2025 16:59:40 +0100 Subject: [PATCH 08/13] Adjust tests to new notify logs Now that we log the type of the notify, some expected log messages in the system tests need to be adjusted accordingly. The bin/tests/system/nsec3/tests_nsec3_retransfer.py log is changed to zone_needdump because it is more reliable. Other tests were adjusted similar in MR !11265, but !11226 introduced a new "sending notify" log line. --- bin/tests/system/notify/tests.sh | 22 +++++++++---------- .../system/nsec3/tests_nsec3_retransfer.py | 2 +- .../tests_xfer_servers_list.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bin/tests/system/notify/tests.sh b/bin/tests/system/notify/tests.sh index 05a9aa8280..d83f5cbee0 100644 --- a/bin/tests/system/notify/tests.sh +++ b/bin/tests/system/notify/tests.sh @@ -45,7 +45,7 @@ for i in 1 2 3 4 5 6 7 8 9 10; do $DIG +tcp -p "${PORT}" example @10.53.0.3 soa >dig.out.ns3.test$n || ret=1 grep "status: NOERROR" dig.out.ns3.test$n >/dev/null || ret=1 grep "flags:.* aa[ ;]" dig.out.ns3.test$n >/dev/null || ret=1 - nr=$(grep -c 'x[0-9].*sending notify to' ns2/named.run) + nr=$(grep -c 'x[0-9].*sending notify(SOA) to' ns2/named.run) [ "$nr" -ge 22 ] || ret=1 [ $ret = 0 ] && break sleep 1 @@ -64,7 +64,7 @@ digcomp dig.out.ns2.test$n dig.out.ns3.test$n || ret=1 test_end test_start "checking startup notify rate limit" -awk '/x[0-9].*sending notify to/ { +awk '/x[0-9].*sending notify\(SOA\) to/ { split($1, ts, "T"); split(ts[2], a, ":"); this = a[1] * 3600 + a[2] * 60 + a[3]; @@ -101,7 +101,7 @@ test_end # See [GL#4689] test_start "checking server behaviour with invalid notify-source-v6 address" -grep "zone ./IN: sending notify to fd92:7065:b8e:fffe::a35:4#" ns1/named.run >/dev/null || ret=1 +grep "zone ./IN: sending notify(SOA) to fd92:7065:b8e:fffe::a35:4#" ns1/named.run >/dev/null || ret=1 grep "dns_request_create: failed address not available" ns1/named.run >/dev/null || ret=1 test_end @@ -121,15 +121,15 @@ test_end if $FEATURETEST --have-fips-dh; then test_start "checking notify over TLS successful" - grep "zone tls-x1/IN: notify to 10.53.0.2#${TLSPORT} successful" ns3/named.run >/dev/null || ret=1 - grep "zone tls-x2/IN: notify to 10.53.0.2#${EXTRAPORT1} successful" ns3/named.run >/dev/null || ret=1 - grep "zone tls-x3/IN: notify to 10.53.0.2#${EXTRAPORT1} successful" ns3/named.run >/dev/null || ret=1 - grep "zone tls-x5/IN: notify to 10.53.0.2#${EXTRAPORT3} successful" ns3/named.run >/dev/null || ret=1 + grep "zone tls-x1/IN: notify(SOA) to 10.53.0.2#${TLSPORT} successful" ns3/named.run >/dev/null || ret=1 + grep "zone tls-x2/IN: notify(SOA) to 10.53.0.2#${EXTRAPORT1} successful" ns3/named.run >/dev/null || ret=1 + grep "zone tls-x3/IN: notify(SOA) to 10.53.0.2#${EXTRAPORT1} successful" ns3/named.run >/dev/null || ret=1 + grep "zone tls-x5/IN: notify(SOA) to 10.53.0.2#${EXTRAPORT3} successful" ns3/named.run >/dev/null || ret=1 test_end test_start "checking notify over TLS failed" - grep "zone tls-x4/IN: notify to 10.53.0.2#${EXTRAPORT1} failed: TLS peer certificate verification failed" ns3/named.run >/dev/null || ret=1 - grep "zone tls-x6/IN: notify to 10.53.0.2#${EXTRAPORT4} failed: TLS peer certificate verification failed" ns3/named.run >/dev/null || ret=1 + grep "zone tls-x4/IN: notify(SOA) to 10.53.0.2#${EXTRAPORT1} failed: TLS peer certificate verification failed" ns3/named.run >/dev/null || ret=1 + grep "zone tls-x6/IN: notify(SOA) to 10.53.0.2#${EXTRAPORT4} failed: TLS peer certificate verification failed" ns3/named.run >/dev/null || ret=1 test_end fi @@ -218,8 +218,8 @@ for i in 1 2 3 4 5 6 7 8 9; do done grep "test string" "$fnb" >/dev/null || ret=1 grep "test string" "$fnc" >/dev/null || ret=1 -grep "sending notify to 10.53.0.5#[0-9]* : TSIG (b)" ns5/named.run >/dev/null || ret=1 -grep "sending notify to 10.53.0.5#[0-9]* : TSIG (c)" ns5/named.run >/dev/null || ret=1 +grep "sending notify(SOA) to 10.53.0.5#[0-9]* : TSIG (b)" ns5/named.run >/dev/null || ret=1 +grep "sending notify(SOA) to 10.53.0.5#[0-9]* : TSIG (c)" ns5/named.run >/dev/null || ret=1 test_end # notify messages were sent to unresponsive 10.53.0.6 during the tests diff --git a/bin/tests/system/nsec3/tests_nsec3_retransfer.py b/bin/tests/system/nsec3/tests_nsec3_retransfer.py index 96a23761b5..7ba4a44845 100644 --- a/bin/tests/system/nsec3/tests_nsec3_retransfer.py +++ b/bin/tests/system/nsec3/tests_nsec3_retransfer.py @@ -123,7 +123,7 @@ def test_nsec3_retransfer(servers, templates): with ns3.watch_log_from_here() as watcher: ns3.rndc(f"retransfer {zone}") # When sending notifies, the zone should be up to date. - watcher.wait_for_line(f"zone {zone}/IN (signed): sending notify to 10.53.0.4") + watcher.wait_for_line(f"zone_needdump: zone {zone}/IN (signed): enter") salt = perform_nsec3_tests(ns3, params) assert prevsalt == salt diff --git a/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py b/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py index 54cf2fcaf2..cb7553d171 100644 --- a/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py +++ b/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py @@ -33,7 +33,7 @@ def wait_for_initial_xfrin(ns): def wait_for_sending_notify(ns1, ns, key_name): pattern = Re( - f"zone test/IN: sending notify to {ns.ip}#[0-9]+ : TSIG \\({key_name}\\)" + f"zone test/IN: sending notify\\(SOA\\) to {ns.ip}#[0-9]+ : TSIG \\({key_name}\\)" ) with ns1.watch_log_from_start() as watcher: watcher.wait_for_line(pattern) From fa5f67fffee146ab390b6b5dc7bec6e191640013 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 30 Oct 2025 09:17:32 +0100 Subject: [PATCH 09/13] Add a function to set NOTIFY(CDS) endpoints This is similar to setting remote endpoints for primaries, secondaries (NOTIFY(SOA)), and parental agents. --- lib/dns/include/dns/zone.h | 14 +++ lib/dns/zone.c | 174 +++++++++++++++---------------------- 2 files changed, 86 insertions(+), 102 deletions(-) diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index ef95a57243..11a9daed4c 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -682,6 +682,20 @@ dns_zone_setalsonotify(dns_zone_t *zone, isc_sockaddr_t *addresses, *\li 'count' to be the number of notifiees. */ +void +dns_zone_setcdsendpoints(dns_zone_t *zone, isc_sockaddr_t *addresses, + isc_sockaddr_t *sources, dns_name_t **keynames, + dns_name_t **tlsnames, uint32_t count); +/*%< + * Set the list of servers to be notified when the zone changes + * its CDS/CDNSKEY RRset. To clear the list use 'count = 0'. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'addresses' to be non-NULL if count != 0. + *\li 'count' to be the number of notifiees. + */ + void dns_zone_unload(dns_zone_t *zone); /*%< diff --git a/lib/dns/zone.c b/lib/dns/zone.c index ee632cef0a..71df6192a3 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -341,6 +341,8 @@ struct dns_zone { dns_remote_t alsonotify; dns_notifyctx_t notifysoa; + + dns_remote_t cds_endpoints; dns_notifyctx_t notifycds; isc_sockaddr_t parentalsrc4; @@ -1102,6 +1104,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx, isc_tid_t tid) { zone->primaries = r; zone->parentals = r; zone->alsonotify = r; + zone->cds_endpoints = r; zone->defaultkasp = NULL; ISC_LIST_INIT(zone->keyring); @@ -1240,6 +1243,7 @@ dns__zone_free(dns_zone_t *zone) { dns_zone_setparentals(zone, NULL, NULL, NULL, NULL, 0); dns_zone_setprimaries(zone, NULL, NULL, NULL, NULL, 0); dns_zone_setalsonotify(zone, NULL, NULL, NULL, NULL, 0); + dns_zone_setcdsendpoints(zone, NULL, NULL, NULL, NULL, 0); zone->check_names = dns_severity_ignore; if (zone->update_acl != NULL) { @@ -6389,46 +6393,6 @@ dns_zone_getnotifysrc6(dns_zone_t *zone, isc_sockaddr_t *notifysrc) { UNLOCK_ZONE(zone); } -void -dns_zone_setalsonotify(dns_zone_t *zone, isc_sockaddr_t *addresses, - isc_sockaddr_t *sources, dns_name_t **keynames, - dns_name_t **tlsnames, uint32_t count) { - dns_remote_t remote; - - REQUIRE(DNS_ZONE_VALID(zone)); - - LOCK_ZONE(zone); - - remote.magic = DNS_REMOTE_MAGIC; - remote.addresses = addresses; - remote.sources = sources; - remote.keynames = keynames; - remote.tlsnames = tlsnames; - remote.addrcnt = count; - - if (dns_remote_equal(&zone->alsonotify, &remote)) { - goto unlock; - } - - dns_remote_clear(&zone->alsonotify); - - /* - * If count == 0, don't allocate any space for servers to notify. - */ - if (count == 0) { - goto unlock; - } - - /* - * Now set up the notify address and key lists. - */ - dns_remote_init(&zone->alsonotify, count, addresses, sources, keynames, - tlsnames, true, zone->mctx); - -unlock: - UNLOCK_ZONE(zone); -} - static bool has_pf(isc_sockaddr_t *addresses, size_t count, int pf) { for (size_t i = 0; i < count; i++) { @@ -6455,57 +6419,81 @@ report_no_active_addresses(dns_zone_t *zone, isc_sockaddr_t *addresses, } } -void -dns_zone_setprimaries(dns_zone_t *zone, isc_sockaddr_t *addresses, - isc_sockaddr_t *sources, dns_name_t **keynames, - dns_name_t **tlsnames, uint32_t count) { - dns_remote_t remote; +static void +setremote(dns_zone_t *zone, dns_remote_t *remote, const char *remotestr, + isc_sockaddr_t *addresses, isc_sockaddr_t *sources, + dns_name_t **keynames, dns_name_t **tlsnames, bool refresh, + bool report, uint32_t count) { + dns_remote_t newremote; REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(DNS_REMOTE_VALID(remote)); - LOCK_ZONE(zone); + newremote.magic = DNS_REMOTE_MAGIC; + newremote.addresses = addresses; + newremote.sources = sources; + newremote.keynames = keynames; + newremote.tlsnames = tlsnames; + newremote.addrcnt = count; - remote.magic = DNS_REMOTE_MAGIC; - remote.addresses = addresses; - remote.sources = sources; - remote.keynames = keynames; - remote.tlsnames = tlsnames; - remote.addrcnt = count; + if (dns_remote_equal(remote, &newremote)) { + return; + } /* - * The refresh code assumes that 'primaries' wouldn't change under it. + * The refresh code assumes that 'servers' wouldn't change under it. * If it will change then kill off any current refresh in progress * and update the primaries info. If it won't change then we can just * unlock and exit. */ - if (!dns_remote_equal(&zone->primaries, &remote)) { - if (zone->request != NULL) { - dns_request_cancel(zone->request); - } - } else { - goto unlock; + if (zone->request != NULL && refresh) { + dns_request_cancel(zone->request); } - dns_remote_clear(&zone->primaries); + dns_remote_clear(remote); /* - * If count == 0, don't allocate any space for primaries. + * If count == 0, don't allocate any space for servers. */ if (count == 0) { - goto unlock; + return; } - report_no_active_addresses(zone, addresses, count, "primaries"); - /* - * Now set up the primaries and primary key lists. + * Now set up the address and key lists. */ - dns_remote_init(&zone->primaries, count, addresses, sources, keynames, - tlsnames, true, zone->mctx); + if (report) { + report_no_active_addresses(zone, addresses, count, remotestr); + } + dns_remote_init(remote, count, addresses, sources, keynames, tlsnames, + true, zone->mctx); +} + +void +dns_zone_setalsonotify(dns_zone_t *zone, isc_sockaddr_t *addresses, + isc_sockaddr_t *sources, dns_name_t **keynames, + dns_name_t **tlsnames, uint32_t count) { + bool refresh = false; + bool report = false; + + LOCK_ZONE(zone); + setremote(zone, &zone->alsonotify, "also-notify", addresses, sources, + keynames, tlsnames, refresh, report, count); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setprimaries(dns_zone_t *zone, isc_sockaddr_t *addresses, + isc_sockaddr_t *sources, dns_name_t **keynames, + dns_name_t **tlsnames, uint32_t count) { + bool refresh = true; + bool report = true; + + LOCK_ZONE(zone); + setremote(zone, &zone->primaries, "primaries", addresses, sources, + keynames, tlsnames, refresh, report, count); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOPRIMARIES); - -unlock: UNLOCK_ZONE(zone); } @@ -6513,43 +6501,25 @@ void dns_zone_setparentals(dns_zone_t *zone, isc_sockaddr_t *addresses, isc_sockaddr_t *sources, dns_name_t **keynames, dns_name_t **tlsnames, uint32_t count) { - dns_remote_t remote; - - REQUIRE(DNS_ZONE_VALID(zone)); + bool refresh = false; + bool report = true; LOCK_ZONE(zone); + setremote(zone, &zone->parentals, "parental-agents", addresses, sources, + keynames, tlsnames, refresh, report, count); + UNLOCK_ZONE(zone); +} - remote.magic = DNS_REMOTE_MAGIC; - remote.addresses = addresses; - remote.sources = sources; - remote.keynames = keynames; - remote.tlsnames = tlsnames; - remote.addrcnt = count; +void +dns_zone_setcdsendpoints(dns_zone_t *zone, isc_sockaddr_t *addresses, + isc_sockaddr_t *sources, dns_name_t **keynames, + dns_name_t **tlsnames, uint32_t count) { + bool refresh = false; + bool report = false; - if (dns_remote_equal(&zone->parentals, &remote)) { - goto unlock; - } - - dns_remote_clear(&zone->parentals); - - /* - * If count == 0, don't allocate any space for parentals. - */ - if (count == 0) { - goto unlock; - } - - report_no_active_addresses(zone, addresses, count, "parental-agents"); - - /* - * Now set up the parentals and parental key lists. - */ - dns_remote_init(&zone->parentals, count, addresses, sources, keynames, - tlsnames, true, zone->mctx); - - dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: set %u parentals", count); - -unlock: + LOCK_ZONE(zone); + setremote(zone, &zone->cds_endpoints, "cds-endpoints", addresses, + sources, keynames, tlsnames, refresh, report, count); UNLOCK_ZONE(zone); } From c8253a0a7a3e6c2ee93ff1eeeed654e95603a358 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 30 Oct 2025 09:48:35 +0100 Subject: [PATCH 10/13] Implement NOTIFY(CDS) logic When the CDS/CDNSKEY RRset gets updated, schedule a NOTIFY(CDS) to be sent to the parental agent. The parental agent is published in the parent zone as a DSYNC RRset, so first we need to figure out the parent owner name. This is done by finding the zonecut (querying for NS RRset until we find a postive answer). In nsfetch_dsync, we then schedule a zone fetch for the DSYNC record at ._dsync.. Then we queue the notify for each target in the DSYNC records that matches the NOTIFY scheme and CDS RRtype. --- lib/dns/include/dns/zonefetch.h | 21 +- lib/dns/zone.c | 411 +++++++++++++++++++++++++++++++- lib/dns/zonefetch.c | 5 +- lib/isc/include/isc/result.h | 1 + lib/isc/result.c | 2 + 5 files changed, 420 insertions(+), 20 deletions(-) diff --git a/lib/dns/include/dns/zonefetch.h b/lib/dns/include/dns/zonefetch.h index 2ea5108281..0a3a357644 100644 --- a/lib/dns/include/dns/zonefetch.h +++ b/lib/dns/include/dns/zonefetch.h @@ -31,20 +31,22 @@ * the last position of the enum. */ typedef enum { + ZONEFETCHTYPE_DSYNC, ZONEFETCHTYPE_KEY, ZONEFETCHTYPE_NS, ZONEFETCHTYPE_COUNT, } dns_zonefetch_type_t; -typedef struct dns_keyfetch dns_keyfetch_t; -typedef struct dns_nsfetch dns_nsfetch_t; -typedef struct dns_zonefetch dns_zonefetch_t; +typedef struct dns_dsyncfetch dns_dsyncfetch_t; +typedef struct dns_keyfetch dns_keyfetch_t; +typedef struct dns_nsfetch dns_nsfetch_t; +typedef struct dns_zonefetch dns_zonefetch_t; /* * Fetch methods. */ typedef struct dns_zonefetch_methods { - void (*start_fetch)(dns_zonefetch_t *fetch); + isc_result_t (*start_fetch)(dns_zonefetch_t *fetch); void (*continue_fetch)(dns_zonefetch_t *fetch); void (*cancel_fetch)(dns_zonefetch_t *fetch); void (*cleanup_fetch)(dns_zonefetch_t *fetch); @@ -55,6 +57,12 @@ typedef struct dns_zonefetch_methods { /* * Fetch contexts. */ +struct dns_dsyncfetch { + dns_fixedname_t fname; + dns_name_t pname; + dns_name_t dsyncname; +}; + struct dns_keyfetch { dns_rdataset_t keydataset; dns_db_t *db; @@ -65,8 +73,9 @@ struct dns_nsfetch { }; typedef union dns_fetchdata { - dns_keyfetch_t keyfetch; - dns_nsfetch_t nsfetch; + dns_dsyncfetch_t dsyncfetch; + dns_keyfetch_t keyfetch; + dns_nsfetch_t nsfetch; } dns_zonefetch_data_t; struct dns_zonefetch { diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 71df6192a3..c4359cf7e1 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -909,6 +910,8 @@ zone_maintenance(dns_zone_t *zone); static void zone_notify(dns_zone_t *zone, isc_time_t *now); static void +zone_notifycds(dns_zone_t *zone); +static void dump_done(void *arg, isc_result_t result); static isc_result_t zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, uint16_t keyid, @@ -10484,12 +10487,14 @@ revocable(dns_zonefetch_t *fetch, dns_rdata_keydata_t *keydata) { /* * Fetch DNSKEY records at the trust anchor name. */ -static void +static isc_result_t keyfetch_start(dns_zonefetch_t *fetch) { REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_KEY); fetch->qname = dns_fixedname_name(&fetch->name); fetch->qtype = dns_rdatatype_dnskey; + + return ISC_R_SUCCESS; } static void @@ -10505,12 +10510,12 @@ keyfetch_cancel(dns_zonefetch_t *fetch) { dns_zone_t *zone; REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_KEY); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); kfetch = &fetch->fetchdata.keyfetch; zone = fetch->zone; - INSIST(LOCKED_ZONE(zone)); - /* * Error during a key fetch; cancel and retry in an hour. */ @@ -10583,6 +10588,8 @@ keyfetch_done(dns_zonefetch_t *fetch, isc_result_t eresult) { REQUIRE(fetch != NULL); REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_KEY); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); kfetch = &fetch->fetchdata.keyfetch; zone = fetch->zone; @@ -20858,7 +20865,7 @@ checkds_send(dns_zone_t *zone) { /* * Fetch NS records from parent zone. */ -static void +static isc_result_t nsfetch_start(dns_zonefetch_t *fetch) { dns_nsfetch_t *nsfetch; unsigned int nlabels = 1; @@ -20874,6 +20881,8 @@ nsfetch_start(dns_zonefetch_t *fetch) { fetch->qtype = dns_rdatatype_ns; fetch->qname = &nsfetch->pname; + + return ISC_R_SUCCESS; } /* @@ -20912,11 +20921,11 @@ nsfetch_cancel(dns_zonefetch_t *fetch) { dns_zone_t *zone; REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_NS); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); zone = fetch->zone; - INSIST(LOCKED_ZONE(zone)); - zone->fetchcount[ZONEFETCHTYPE_NS]--; } @@ -20931,7 +20940,7 @@ nsfetch_cleanup(dns_zonefetch_t *fetch) { * agents. */ static isc_result_t -nsfetch_done(dns_zonefetch_t *fetch, isc_result_t eresult) { +nsfetch_checkds(dns_zonefetch_t *fetch, isc_result_t eresult) { dns_nsfetch_t *nsfetch; isc_result_t result = ISC_R_NOMORE; dns_zone_t *zone = NULL; @@ -20941,6 +20950,8 @@ nsfetch_done(dns_zonefetch_t *fetch, isc_result_t eresult) { REQUIRE(fetch != NULL); REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_NS); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); nsfetch = &fetch->fetchdata.nsfetch; zone = fetch->zone; @@ -20951,7 +20962,7 @@ nsfetch_done(dns_zonefetch_t *fetch, isc_result_t eresult) { dns_name_format(pname, pnamebuf, sizeof(pnamebuf)); dnssec_log(zone, ISC_LOG_DEBUG(3), - "Returned from '%s' NS fetch in nsfetch_done(): %s", + "Returned from '%s' NS fetch in nsfetch_checkds(): %s", pnamebuf, isc_result_totext(eresult)); if (eresult == DNS_R_NCACHENXRRSET || eresult == DNS_R_NXRRSET) { @@ -21092,7 +21103,7 @@ zone_checkds(dns_zone_t *zone) { .continue_fetch = nsfetch_continue, .cancel_fetch = nsfetch_cancel, .cleanup_fetch = nsfetch_cleanup, - .done_fetch = nsfetch_done, + .done_fetch = nsfetch_checkds, }, }; isc_mem_attach(zone->mctx, &fetch->mctx); @@ -21117,6 +21128,374 @@ zone_checkds(dns_zone_t *zone) { #endif /* ifdef ENABLE_AFL */ } +static isc_result_t +dsyncfetch_start(dns_zonefetch_t *fetch) { + dns_dsyncfetch_t *dsyncfetch; + dns_zone_t *zone; + dns_fixedname_t fndl, fnp; + dns_name_t *name, *dsyncname, *dsynclabel, *prefix; + unsigned int nlabels; + isc_result_t result; + + REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_DSYNC); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + + dsyncfetch = &fetch->fetchdata.dsyncfetch; + zone = fetch->zone; + + /* + * The dsync owner name is build up of ._dsync.. + * The prefix is the relative domain name of the child consisting of + * the labels under the zonecut. + */ + dsyncname = dns_fixedname_initname(&dsyncfetch->fname); + dsynclabel = dns_fixedname_initname(&fndl); + dns_name_fromstring(dsynclabel, "_dsync", NULL, 0, fetch->mctx); + result = dns_name_concatenate(dsynclabel, &dsyncfetch->pname, + dsyncname); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "dsyncfetch: failed to create parent DSYNC fetch " + "(parent part): %s", + isc_result_totext(result)); + return result; + } + + name = dns_fixedname_name(&fetch->name); + nlabels = dns_name_countlabels(&dsyncfetch->pname); + prefix = dns_fixedname_initname(&fnp); + dns_name_split(name, nlabels, prefix, NULL); + result = dns_name_concatenate(prefix, dsyncname, dsyncname); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "dsyncfetch: failed to create parent DSYNC fetch " + "(child part): %s", + isc_result_totext(result)); + return result; + } + + dns_name_init(&dsyncfetch->dsyncname); + dns_name_clone(dsyncname, &dsyncfetch->dsyncname); + + fetch->qtype = dns_rdatatype_dsync; + fetch->qname = &dsyncfetch->dsyncname; + + return ISC_R_SUCCESS; +} + +/* + * Retry an DSYNC RRset lookup. + */ +static void +dsyncfetch_continue(dns_zonefetch_t *fetch) { + dns_zone_t *zone; + + REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_DSYNC); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + + zone = fetch->zone; + +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) { +#endif /* ifdef ENABLE_AFL */ + LOCK_ZONE(zone); + zone->fetchcount[ZONEFETCHTYPE_DSYNC]++; + + dns_zonefetch_reschedule(fetch); + + if (isc_log_wouldlog(ISC_LOG_DEBUG(3))) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "Creating parent DSYNC fetch in " + "dsyncfetch_continue()"); + } + UNLOCK_ZONE(zone); +#ifdef ENABLE_AFL + } +#endif /* ifdef ENABLE_AFL */ +} + +static void +dsyncfetch_cancel(dns_zonefetch_t *fetch) { + dns_zone_t *zone; + + REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_DSYNC); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); + + zone = fetch->zone; + + zone->fetchcount[ZONEFETCHTYPE_DSYNC]--; +} + +static void +dsyncfetch_cleanup(dns_zonefetch_t *fetch) { + REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_DSYNC); +} + +/* + * A DSYNC RRset has been fetched; scan the RRset and start sending + * NOTIFY(CDS) queries to them. + */ +static isc_result_t +dsyncfetch_done(dns_zonefetch_t *fetch, isc_result_t eresult) { + dns_dsyncfetch_t *dsyncfetch; + isc_result_t result = ISC_R_NOMORE; + dns_notify_t *notify = NULL; + dns_zone_t *zone = NULL; + dns_name_t *dsyncname = NULL; + char dsyncnamebuf[DNS_NAME_FORMATSIZE]; + dns_rdataset_t *rrset = NULL; + + REQUIRE(fetch != NULL); + REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_DSYNC); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); + + dsyncfetch = &fetch->fetchdata.dsyncfetch; + zone = fetch->zone; + rrset = &fetch->rrset; + dsyncname = &dsyncfetch->dsyncname; + + zone->fetchcount[ZONEFETCHTYPE_DSYNC]--; + + dns_name_format(dsyncname, dsyncnamebuf, sizeof(dsyncnamebuf)); + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "dsyncfetch: Returned from '%s' DSYNC fetch in " + "dsyncfetch_done(): %s", + dsyncnamebuf, isc_result_totext(eresult)); + + result = dns_zonefetch_verify(fetch, eresult, dns_trust_secure); + if (result != ISC_R_SUCCESS) { + goto done; + } + + UNLOCK_ZONE(zone); + + /* Notify targets. */ + dns_rdata_dsync_t dsync; + unsigned int count = 0; + for (result = dns_rdataset_first(rrset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rrset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(rrset, &rdata); + result = dns_rdata_tostruct(&rdata, &dsync, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdata_reset(&rdata); + if (dsync.scheme != DNS_DSYNCSCHEME_NOTIFY) { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "dsyncfetch: unsupported DSYNC scheme %u, " + "ignoring", + dsync.scheme); + continue; + } + + if (dsync.type != dns_rdatatype_cds) { + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(dsync.type, typebuf, + sizeof(typebuf)); + + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "dsyncfetch: DSYNC RRtype %s not " + "supported, ignoring", + result == ISC_R_SUCCESS ? typebuf + : "UNKNOWN"); + continue; + } + + count++; + if (count > 1) { + dns_zone_log(zone, ISC_LOG_WARNING, + "dsyncfetch: multiple DSYNC records " + "matching NOTIFY scheme and CDS RRtype, " + "dropping response"); + result = DNS_R_INVALIDDSYNC; + break; + } + } + + LOCK_ZONE(zone); + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } else { + goto done; + } + + bool isqueued = dns_notify_isqueued(&zone->notifycds, dns_rdatatype_cds, + dsync.port, 0, &dsync.target, NULL, + NULL, NULL); + + UNLOCK_ZONE(zone); + + if (!isqueued) { + dns_notify_create(zone->mctx, dns_rdatatype_cds, dsync.port, + DNS_NOTIFY_NOSOA, ¬ify); + if (isc_log_wouldlog(ISC_LOG_DEBUG(3))) { + char tbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&dsync.target, tbuf, sizeof(tbuf)); + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "dsyncfetch: send NOTIFY(CDS) query to %s", + tbuf); + } + dns_zone_iattach(zone, ¬ify->zone); + dns_name_dup(&dsync.target, zone->mctx, ¬ify->ns); + LOCK_ZONE(zone); + ISC_LIST_APPEND(zone->notifycds.notifies, notify, link); + UNLOCK_ZONE(zone); + dns_notify_find_address(notify); + } + + LOCK_ZONE(zone); + +done: + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "dsyncfetch: error processing DSYNC RRset: %s", + isc_result_totext(result)); + } + + return result; +} + +/* + * An NS RRset has been fetched from the parent of a zone whose DSYNC RRset + * needs to be queried; scan the RRset and start resolving those queries. + */ +static isc_result_t +nsfetch_dsync(dns_zonefetch_t *fetch, isc_result_t eresult) { + dns_nsfetch_t *nsfetch; + isc_result_t result = ISC_R_NOMORE; + dns_zone_t *zone = NULL; + dns_name_t *pname = NULL; + char pnamebuf[DNS_NAME_FORMATSIZE]; + + REQUIRE(fetch != NULL); + REQUIRE(fetch->fetchtype == ZONEFETCHTYPE_NS); + REQUIRE(DNS_ZONE_VALID(fetch->zone)); + REQUIRE(LOCKED_ZONE(fetch->zone)); + + nsfetch = &fetch->fetchdata.nsfetch; + zone = fetch->zone; + pname = &nsfetch->pname; + + zone->fetchcount[ZONEFETCHTYPE_NS]--; + + dns_name_format(pname, pnamebuf, sizeof(pnamebuf)); + dnssec_log(zone, ISC_LOG_DEBUG(3), + "Returned from '%s' NS fetch in nsfetch_dsync(): %s", + pnamebuf, isc_result_totext(eresult)); + + if (eresult == DNS_R_NCACHENXRRSET || eresult == DNS_R_NXRRSET) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "NODATA response for NS '%s', level up", pnamebuf); + return DNS_R_CONTINUE; + } + + result = dns_zonefetch_verify(fetch, eresult, dns_trust_secure); + if (result != ISC_R_SUCCESS) { + goto done; + } + +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) { +#endif /* ifdef ENABLE_AFL */ + dns_zonefetch_t *zfetch = NULL; + dns_dsyncfetch_t *dsyncfetch; + + zfetch = isc_mem_get(zone->mctx, sizeof(dns_zonefetch_t)); + *zfetch = (dns_zonefetch_t){ + .zone = zone, + .options = DNS_FETCHOPT_UNSHARED | + DNS_FETCHOPT_NOCACHED, + .fetchtype = ZONEFETCHTYPE_DSYNC, + .fetchmethods = + (dns_zonefetch_methods_t){ + .start_fetch = dsyncfetch_start, + .continue_fetch = dsyncfetch_continue, + .cancel_fetch = dsyncfetch_cancel, + .cleanup_fetch = dsyncfetch_cleanup, + .done_fetch = dsyncfetch_done, + }, + }; + isc_mem_attach(zone->mctx, &zfetch->mctx); + + zone->fetchcount[ZONEFETCHTYPE_DSYNC]++; + + dsyncfetch = &zfetch->fetchdata.dsyncfetch; + dns_name_init(&dsyncfetch->pname); + dns_name_clone(pname, &dsyncfetch->pname); + + dns_zonefetch_schedule(zfetch, &zone->origin); + + if (isc_log_wouldlog(ISC_LOG_DEBUG(3))) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "Creating parent DSYNC fetch in " + "nsfetch_dsync()"); + } +#ifdef ENABLE_AFL + } +#endif /* ifdef ENABLE_AFL */ + +done: + return result; +} + +static void +zone_notifycds(dns_zone_t *zone) { + dns_notifytype_t notifytype = zone->notifycds.notifytype; + + if (notifytype == dns_notifytype_no) { + return; + } + + INSIST(notifytype == dns_notifytype_yes); + +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) { +#endif /* ifdef ENABLE_AFL */ + dns_zonefetch_t *fetch = NULL; + dns_nsfetch_t *nsfetch; + + fetch = isc_mem_get(zone->mctx, sizeof(dns_zonefetch_t)); + *fetch = (dns_zonefetch_t){ + .zone = zone, + .options = DNS_FETCHOPT_UNSHARED | + DNS_FETCHOPT_NOCACHED, + .fetchtype = ZONEFETCHTYPE_NS, + .fetchmethods = + (dns_zonefetch_methods_t){ + .start_fetch = nsfetch_start, + .continue_fetch = nsfetch_continue, + .cancel_fetch = nsfetch_cancel, + .cleanup_fetch = nsfetch_cleanup, + .done_fetch = nsfetch_dsync, + }, + }; + isc_mem_attach(zone->mctx, &fetch->mctx); + + LOCK_ZONE(zone); + zone->fetchcount[ZONEFETCHTYPE_NS]++; + + nsfetch = &fetch->fetchdata.nsfetch; + dns_name_init(&nsfetch->pname); + dns_name_clone(&zone->origin, &nsfetch->pname); + + dns_zonefetch_schedule(fetch, &zone->origin); + + if (isc_log_wouldlog(ISC_LOG_DEBUG(3))) { + dnssec_log( + zone, ISC_LOG_DEBUG(3), + "Creating parent NS fetch in zone_notifyds()"); + } + UNLOCK_ZONE(zone); +#ifdef ENABLE_AFL + } +#endif /* ifdef ENABLE_AFL */ +} + static void update_ttl(dns_rdataset_t *rdataset, dns_name_t *name, dns_ttl_t ttl, dns_diff_t *diff) { @@ -21262,6 +21641,7 @@ zone_rekey(dns_zone_t *zone) { bool commit = false, newactive = false; bool newalg = false; bool fullsign; + bool notifycds = false; bool offlineksk = false; bool kasp_change = false; uint8_t options = 0; @@ -21627,8 +22007,7 @@ zone_rekey(dns_zone_t *zone) { &cdnskeyset, now, &digests, cdnskeypub, ttl, &diff, mctx); if (result == ISC_R_SUCCESS) { - dnssec_log(zone, ISC_LOG_DEBUG(3), - "zone_rekey:CDS/CDNSKEY updated"); + notifycds = true; } else if (result != DNS_R_UNCHANGED) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY: %s", @@ -21666,8 +22045,7 @@ zone_rekey(dns_zone_t *zone) { &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl, &diff, mctx, cdsdel, cdnskeydel); if (result == ISC_R_SUCCESS) { - dnssec_log(zone, ISC_LOG_DEBUG(3), - "zone_rekey:CDS/CDNSKEY updated (DELETE)"); + notifycds = true; } else if (result != DNS_R_UNCHANGED) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY " @@ -21983,6 +22361,13 @@ zone_rekey(dns_zone_t *zone) { ISC_LIST_APPEND(zone->keyring, key, link); } + /* + * If the CDS/CDNSKEY RRset has changed, send NOTIFY(CDS) to endpoints. + */ + if (notifycds) { + zone_notifycds(zone); + } + result = ISC_R_SUCCESS; cleanup: diff --git a/lib/dns/zonefetch.c b/lib/dns/zonefetch.c index 27319cc9ef..d8a5df9b18 100644 --- a/lib/dns/zonefetch.c +++ b/lib/dns/zonefetch.c @@ -43,7 +43,10 @@ dns_zonefetch_run(void *arg) { INSIST(view != NULL); INSIST(loop != NULL); - fetch->fetchmethods.start_fetch(fetch); + result = fetch->fetchmethods.start_fetch(fetch); + if (result != ISC_R_SUCCESS) { + goto cancel; + } result = dns_view_getresolver(view, &resolver); if (result != ISC_R_SUCCESS) { diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h index 34f08a7f74..3c5d6a75d7 100644 --- a/lib/isc/include/isc/result.h +++ b/lib/isc/include/isc/result.h @@ -209,6 +209,7 @@ typedef enum isc_result { DNS_R_NOSKRFILE, DNS_R_NOSKRBUNDLE, DNS_R_LOOPDETECTED, + DNS_R_INVALIDDSYNC, DST_R_UNSUPPORTEDALG, DST_R_CRYPTOFAILURE, diff --git a/lib/isc/result.c b/lib/isc/result.c index de78e4422c..f362d812f7 100644 --- a/lib/isc/result.c +++ b/lib/isc/result.c @@ -212,6 +212,7 @@ static const char *description[ISC_R_NRESULTS] = { [DNS_R_NOSKRFILE] = "no SKR file", [DNS_R_NOSKRBUNDLE] = "no available SKR bundle", [DNS_R_LOOPDETECTED] = "fetch loop detected", + [DNS_R_INVALIDDSYNC] = "invalid DSYNC response", [DST_R_UNSUPPORTEDALG] = "algorithm is unsupported", [DST_R_CRYPTOFAILURE] = "crypto failure", @@ -445,6 +446,7 @@ static const char *identifier[ISC_R_NRESULTS] = { [DNS_R_NOSKRFILE] = "DNS_R_NOSKRFILE", [DNS_R_NOSKRBUNDLE] = "DNS_R_NOSKRBUNDLE", [DNS_R_LOOPDETECTED] = "DNS_R_LOOPDETECTED", + [DNS_R_INVALIDDSYNC] = "DNS_R_INVALIDDSYNC", [DST_R_UNSUPPORTEDALG] = "DST_R_UNSUPPORTEDALG", [DST_R_CRYPTOFAILURE] = "DST_R_CRYPTOFAILURE", From e344fe18bcc8ca2204470717ddfe70dbba293c8e Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 25 Nov 2025 08:56:32 +0100 Subject: [PATCH 11/13] Test sending NOTIFY(CDS) messages When starting up the services, send notifies for the existing CDS RRset. This requires setting up a chain of trust for the test, so the DSYNC records can be retrieved and validated. This feature requires enabling 'notify-cds' and 'dnssec-validation'. In this test, the scanner is pointed to ns2. Since there is no code for receiving NOTIFY(CDS) messages for delegations, this is treated as "not authoritative". Checking for this log message ensures us that the NOTIFY(CDS) message was actually sent. --- .../system/multisigner/ns1/named.conf.j2 | 33 +++++++++++ bin/tests/system/multisigner/ns1/root.db.in | 27 +++++++++ bin/tests/system/multisigner/ns1/setup.sh | 41 ++++++++++++++ .../multisigner/ns2/multisigner.db.in.j2 | 34 ++++++++++++ .../system/multisigner/ns2/named.conf.j2 | 53 ++++++++++++++++++ .../system/multisigner/ns2/secondary.db.in.j2 | 36 ++++++++++++ bin/tests/system/multisigner/ns2/setup.sh | 38 +++++++++++++ .../system/multisigner/ns3/named.conf.j2 | 12 +++- bin/tests/system/multisigner/ns3/setup.sh | 12 ++-- .../system/multisigner/ns4/named.conf.j2 | 12 +++- bin/tests/system/multisigner/ns4/setup.sh | 12 ++-- .../system/multisigner/ns5/named.conf.j2 | 8 ++- bin/tests/system/multisigner/setup.sh | 11 ++++ .../system/multisigner/tests_multisigner.py | 55 ++++++++++++++++--- 14 files changed, 359 insertions(+), 25 deletions(-) create mode 100644 bin/tests/system/multisigner/ns1/named.conf.j2 create mode 100644 bin/tests/system/multisigner/ns1/root.db.in create mode 100644 bin/tests/system/multisigner/ns1/setup.sh create mode 100644 bin/tests/system/multisigner/ns2/multisigner.db.in.j2 create mode 100644 bin/tests/system/multisigner/ns2/named.conf.j2 create mode 100644 bin/tests/system/multisigner/ns2/secondary.db.in.j2 create mode 100644 bin/tests/system/multisigner/ns2/setup.sh diff --git a/bin/tests/system/multisigner/ns1/named.conf.j2 b/bin/tests/system/multisigner/ns1/named.conf.j2 new file mode 100644 index 0000000000..86cd34ca3a --- /dev/null +++ b/bin/tests/system/multisigner/ns1/named.conf.j2 @@ -0,0 +1,33 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS1 + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +zone "." { + type primary; + file "root.db.signed"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/multisigner/ns1/root.db.in b/bin/tests/system/multisigner/ns1/root.db.in new file mode 100644 index 0000000000..395e28089e --- /dev/null +++ b/bin/tests/system/multisigner/ns1/root.db.in @@ -0,0 +1,27 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +. IN SOA gson.nominum.com. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +multisigner. NS ns2.multisigner. +ns2.multisigner. A 10.53.0.2 + +secondary. NS ns2.secondary. +ns2.secondary. A 10.53.0.2 diff --git a/bin/tests/system/multisigner/ns1/setup.sh b/bin/tests/system/multisigner/ns1/setup.sh new file mode 100644 index 0000000000..293f6ff192 --- /dev/null +++ b/bin/tests/system/multisigner/ns1/setup.sh @@ -0,0 +1,41 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +set -e + +zone=. +infile=root.db.in +zonefile=root.db + +echo_i "ns1/setup.sh" + +for tld in multisigner secondary; do + cp "../ns2/dsset-${tld}." . +done + +KSK=$($KEYGEN -q -fk -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS $zone) + +cat $infile $KSK.key $ZSK.key >$zonefile + +$SIGNER -g -o $zone $zonefile >/dev/null 2>&1 + +# Configure the resolving server with a static key. +keyfile_to_static_ds "$KSK" >trusted.conf +cp trusted.conf ../ns2/trusted.conf +cp trusted.conf ../ns3/trusted.conf +cp trusted.conf ../ns4/trusted.conf +cp trusted.conf ../ns5/trusted.conf diff --git a/bin/tests/system/multisigner/ns2/multisigner.db.in.j2 b/bin/tests/system/multisigner/ns2/multisigner.db.in.j2 new file mode 100644 index 0000000000..db9c07981c --- /dev/null +++ b/bin/tests/system/multisigner/ns2/multisigner.db.in.j2 @@ -0,0 +1,34 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +$ORIGIN multisigner. + +multisigner. IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + +scanner A 10.53.0.2 + +model2 NS ns3 + NS ns4 + +ns3.model2 A 10.53.0.3 +ns4.model2 A 10.53.0.4 + +*._dsync DSYNC CDS NOTIFY @PORT@ scanner diff --git a/bin/tests/system/multisigner/ns2/named.conf.j2 b/bin/tests/system/multisigner/ns2/named.conf.j2 new file mode 100644 index 0000000000..951c840d49 --- /dev/null +++ b/bin/tests/system/multisigner/ns2/named.conf.j2 @@ -0,0 +1,53 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + allow-notify { 10.53.0.3; 10.53.0.4; }; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "multisigner" { + type primary; + file "multisigner.db.signed"; +}; + +zone "secondary" { + type primary; + file "secondary.db.signed"; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/multisigner/ns2/secondary.db.in.j2 b/bin/tests/system/multisigner/ns2/secondary.db.in.j2 new file mode 100644 index 0000000000..d108187ff2 --- /dev/null +++ b/bin/tests/system/multisigner/ns2/secondary.db.in.j2 @@ -0,0 +1,36 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +$ORIGIN secondary. + +secondary. IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + +scanner A 10.53.0.2 + +model2 NS ns3 + NS ns4 + +ns3.model2 A 10.53.0.3 +ns4.model2 A 10.53.0.4 + +model2._dsync DSYNC CSYNC NOTIFY @PORT@ scanner ; ignored, unknown rrtype +model2._dsync DSYNC DSYNC NOTIFY @PORT@ scanner ; ignored, bad rrtype +model2._dsync DSYNC CDS NOTIFY @PORT@ scanner diff --git a/bin/tests/system/multisigner/ns2/setup.sh b/bin/tests/system/multisigner/ns2/setup.sh new file mode 100644 index 0000000000..025c5d3204 --- /dev/null +++ b/bin/tests/system/multisigner/ns2/setup.sh @@ -0,0 +1,38 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +echo_i "ns2/setup.sh" + +setup() { + zone="$1" + echo_i "setting up zone: $zone" + infile="${zone}.db.in" + zonefile="${zone}.db" + + cp ../ns3/dsset-ns3-model2.$zone. . + cp ../ns4/dsset-ns4-model2.$zone. . + + KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 -f KSK $zone) + ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zone) + $DSFROMKEY $KSK.key >dsset-ns2-${zone}. + + cat $infile $KSK.key $ZSK.key >$zonefile + $SIGNER -g -o $zone $zonefile + # >/dev/null 2>&1 +} + +setup "multisigner" +setup "secondary" diff --git a/bin/tests/system/multisigner/ns3/named.conf.j2 b/bin/tests/system/multisigner/ns3/named.conf.j2 index 7cf25bbc44..fc51c882f4 100644 --- a/bin/tests/system/multisigner/ns3/named.conf.j2 +++ b/bin/tests/system/multisigner/ns3/named.conf.j2 @@ -24,9 +24,9 @@ options { listen-on { 10.53.0.3; }; listen-on-v6 { none; }; allow-transfer { any; }; - recursion no; key-directory "."; - dnssec-validation no; + dnssec-validation yes; + notify-cds yes; }; key rndc_key { @@ -52,4 +52,12 @@ zone "model2.secondary." { file "model2.secondary.db"; dnssec-policy model2; inline-signing yes; + notify no; }; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/multisigner/ns3/setup.sh b/bin/tests/system/multisigner/ns3/setup.sh index 80327a1300..2194f4e8d8 100644 --- a/bin/tests/system/multisigner/ns3/setup.sh +++ b/bin/tests/system/multisigner/ns3/setup.sh @@ -23,20 +23,20 @@ zsktimes="-P now -A now" zone="model2.multisigner" echo_i "setting up zone: $zone" zonefile="${zone}.db" - -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns3-${zone}. zone="model2.secondary" echo_i "setting up zone: $zone" zonefile="${zone}.db" cp "../ns5/${zonefile}.in" "$zonefile" - -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns3-${zone}. # ZSK will be added to the other provider with nsupdate. cat "${ZSK}.key" | grep -v ";.*" >"${zone}.zsk" diff --git a/bin/tests/system/multisigner/ns4/named.conf.j2 b/bin/tests/system/multisigner/ns4/named.conf.j2 index 3f36f76cd5..017dd6c50a 100644 --- a/bin/tests/system/multisigner/ns4/named.conf.j2 +++ b/bin/tests/system/multisigner/ns4/named.conf.j2 @@ -24,9 +24,9 @@ options { listen-on { 10.53.0.4; }; listen-on-v6 { none; }; allow-transfer { any; }; - recursion no; key-directory "."; - dnssec-validation no; + dnssec-validation yes; + notify-cds yes; }; key rndc_key { @@ -52,4 +52,12 @@ zone "model2.secondary." { file "model2.secondary.db"; dnssec-policy model2; inline-signing yes; + notify no; }; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/multisigner/ns4/setup.sh b/bin/tests/system/multisigner/ns4/setup.sh index c58ddce495..d50d3bd26c 100644 --- a/bin/tests/system/multisigner/ns4/setup.sh +++ b/bin/tests/system/multisigner/ns4/setup.sh @@ -23,20 +23,20 @@ zsktimes="-P now -A now" zone="model2.multisigner" echo_i "setting up zone: $zone" zonefile="${zone}.db" - -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns4-${zone}. zone="model2.secondary" echo_i "setting up zone: $zone" zonefile="${zone}.db" cp "../ns5/${zonefile}.in" "$zonefile" - -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns4-${zone}. # ZSK will be added to the other provider with nsupdate. cat "${ZSK}.key" | grep -v ";.*" >"${zone}.zsk" diff --git a/bin/tests/system/multisigner/ns5/named.conf.j2 b/bin/tests/system/multisigner/ns5/named.conf.j2 index eeae1309c5..f6cf15e991 100644 --- a/bin/tests/system/multisigner/ns5/named.conf.j2 +++ b/bin/tests/system/multisigner/ns5/named.conf.j2 @@ -24,7 +24,6 @@ options { listen-on { 10.53.0.5; }; listen-on-v6 { none; }; allow-transfer { any; }; - recursion no; key-directory "."; dnssec-validation no; notify-delay 0; @@ -44,3 +43,10 @@ zone "model2.secondary." { allow-update { any; }; file "model2.secondary.db"; }; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/multisigner/setup.sh b/bin/tests/system/multisigner/setup.sh index af72460917..12d7073d26 100644 --- a/bin/tests/system/multisigner/setup.sh +++ b/bin/tests/system/multisigner/setup.sh @@ -16,6 +16,7 @@ set -e +# multi-signers ( cd ns3 $SHELL setup.sh @@ -28,3 +29,13 @@ set -e cd ns5 $SHELL setup.sh ) +# tld +( + cd ns2 + $SHELL setup.sh +) +# root +( + cd ns1 + $SHELL setup.sh +) diff --git a/bin/tests/system/multisigner/tests_multisigner.py b/bin/tests/system/multisigner/tests_multisigner.py index 6fdb177bd9..bc53c24975 100644 --- a/bin/tests/system/multisigner/tests_multisigner.py +++ b/bin/tests/system/multisigner/tests_multisigner.py @@ -33,16 +33,20 @@ pytestmark = pytest.mark.extra_artifacts( "secondary.cds.ns*", "unused.*", "verify.out.*", - "ns*/K*", - "ns*/db-*", - "ns*/keygen.out.*", + "ns*/*.db", + "ns*/*.db.in", "ns*/*.jbk", "ns*/*.jnl", - "ns*/*.zsk", - "ns*/*.signed", "ns*/*.journal.out.*", + "ns*/*.signed", + "ns*/*.zsk", + "ns*/db-*", + "ns*/dsset-*", + "ns*/K*", + "ns*/keygen.out.*", + "ns*/managed-keys.bind*", "ns*/settime.out.*", - "ns*/model2.secondary.db", + "ns*/trusted.conf", ] ) @@ -503,7 +507,7 @@ def check_remove_cds( check_dnssec(server, zone, keys, expected) -def test_multisigner(ns3, ns4): +def test_multisigner(ns2, ns3, ns4): zone = "model2.multisigner" keyprops = [ f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", @@ -515,6 +519,23 @@ def test_multisigner(ns3, ns4): isctest.kasp.wait_keymgr_done(ns3, zone) isctest.kasp.wait_keymgr_done(ns4, zone) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN: dsyncfetch: send NOTIFY(CDS) query to scanner.multisigner" + ) + + with ns4.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.multisigner" + ) + + with ns2.watch_log_from_start() as watcher: + # Receiving NOTIFY(CDS) has not been implemented yet. Until + # then, notifies for child zones towards the parent result in + # not authoritative (unless child and parent are served by the + # same name server). + watcher.wait_for_line(f"received notify for zone '{zone}': NOTAUTH") + keys3 = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) ksks3 = [k for k in keys3 if k.is_ksk()] zsks3 = [k for k in keys3 if not k.is_ksk()] @@ -574,7 +595,7 @@ def test_multisigner(ns3, ns4): check_no_dnssec_in_journal(ns4, zone) -def test_multisigner_secondary(ns3, ns4, ns5): +def test_multisigner_secondary(ns2, ns3, ns4, ns5): zone = "model2.secondary" keyprops = [ f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", @@ -586,6 +607,24 @@ def test_multisigner_secondary(ns3, ns4, ns5): isctest.kasp.wait_keymgr_done(ns3, zone) isctest.kasp.wait_keymgr_done(ns4, zone) + for server in [ns3, ns4]: + with server.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.secondary" + ) + + msg = f"zone {zone}/IN (signed): dsyncfetch: DSYNC RRtype CSYNC not supported, ignoring" + assert msg in server.log + msg = f"zone {zone}/IN (signed): dsyncfetch: DSYNC RRtype DSYNC not supported, ignoring" + assert msg in server.log + + with ns2.watch_log_from_start() as watcher: + # Receiving NOTIFY(CDS) has not been implemented yet. Until + # then, notifies for child zones towards the parent result in + # not authoritative (unless child and parent are served by the + # same name server). + watcher.wait_for_line(f"received notify for zone '{zone}': NOTAUTH") + keys3 = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) ksks3 = [k for k in keys3 if k.is_ksk()] zsks3 = [k for k in keys3 if not k.is_ksk()] From 35a7024e8cdef455fd19f2f4da801dc0e007f307 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 1 Dec 2025 14:20:01 +0100 Subject: [PATCH 12/13] Test sending NOTIFY(CDS) messages during rollover When doing rollover and the CDS/CDNSKEY RRset is updated, test that a NOTIFY(CDS) message is sent. For other steps in the rollover, prohibit any dsyncfetch activity. --- .../tests_rollover_algo_csk_initial.py | 2 ++ .../tests_rollover_algo_csk_reconfig.py | 15 +++++++++++++++ .../tests_rollover_algo_ksk_zsk_initial.py | 2 ++ .../tests_rollover_algo_ksk_zsk_reconfig.py | 15 +++++++++++++++ .../tests_rollover_csk_roll1.py | 17 +++++++++++++++++ .../tests_rollover_csk_roll2.py | 17 +++++++++++++++++ .../tests_rollover_enable_dnssec.py | 11 +++++++++++ .../tests_rollover_going_insecure_initial.py | 2 ++ .../tests_rollover_going_insecure_reconfig.py | 18 +++++++++++++++--- .../tests_rollover_three_is_a_crowd.py | 5 +++++ .../tests_rollover_ksk_doubleksk.py | 15 +++++++++++++++ .../tests_rollover_lifetime_initial.py | 2 ++ .../tests_rollover_lifetime_reconfig.py | 2 ++ .../tests_rollover_straight2none_initial.py | 2 ++ .../tests_rollover_straight2none_reconfig.py | 2 ++ .../tests_rollover_zsk_prepublication.py | 12 ++++++++++++ .../system/rollover/ns3/named.common.conf.j2 | 1 + 17 files changed, 137 insertions(+), 3 deletions(-) diff --git a/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py b/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py index 131933f40f..069583179a 100644 --- a/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py +++ b/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py @@ -80,3 +80,5 @@ def test_algoroll_csk_initial(tld, ns3): "nextev": TIMEDELTA["PT1H"], } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py b/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py index 254f438dd8..5ef6131a85 100644 --- a/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py +++ b/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py @@ -138,6 +138,8 @@ def test_algoroll_csk_reconfig_step1(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -175,6 +177,8 @@ def test_algoroll_csk_reconfig_step2(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -244,6 +248,11 @@ def test_algoroll_csk_reconfig_step3(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.{tld}" + ) + @pytest.mark.parametrize( "tld", @@ -299,6 +308,8 @@ def test_algoroll_csk_reconfig_step4(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -333,6 +344,8 @@ def test_algoroll_csk_reconfig_step5(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -365,3 +378,5 @@ def test_algoroll_csk_reconfig_step6(tld, ns3, alg, size): "verbose": True, } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py b/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py index 2f74cd0fa4..826d8a1254 100644 --- a/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py +++ b/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py @@ -77,3 +77,5 @@ def test_algoroll_ksk_zsk_initial(tld, ns3): "nextev": TIMEDELTA["PT1H"], } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py b/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py index 67de084d46..a1b2688212 100644 --- a/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py +++ b/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py @@ -141,6 +141,8 @@ def test_algoroll_ksk_zsk_reconfig_step1(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -180,6 +182,8 @@ def test_algoroll_ksk_zsk_reconfig_step2(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -253,6 +257,11 @@ def test_algoroll_ksk_zsk_reconfig_step3(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.{tld}" + ) + @pytest.mark.parametrize( "tld", @@ -315,6 +324,8 @@ def test_algoroll_ksk_zsk_reconfig_step4(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -353,6 +364,8 @@ def test_algoroll_ksk_zsk_reconfig_step5(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -387,3 +400,5 @@ def test_algoroll_ksk_zsk_reconfig_step6(tld, ns3, alg, size): "verbose": True, } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py b/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py index 5e496dbff6..03acab2ce7 100644 --- a/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py +++ b/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py @@ -123,6 +123,8 @@ def test_csk_roll1_step1(tld, ns3, alg, size): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -181,6 +183,8 @@ def test_csk_roll1_step2(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -268,6 +272,11 @@ def test_csk_roll1_step3(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.{tld}" + ) + @pytest.mark.parametrize( "tld", @@ -333,6 +342,8 @@ def test_csk_roll1_step4(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -366,6 +377,8 @@ def test_csk_roll1_step5(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -402,6 +415,8 @@ def test_csk_roll1_step6(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -461,3 +476,5 @@ def test_csk_roll1_step8(tld, alg, size, ns3): "nextev": None, } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py b/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py index 529fd836ee..d08aaa2d40 100644 --- a/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py +++ b/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py @@ -126,6 +126,8 @@ def test_csk_roll2_step1(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -184,6 +186,8 @@ def test_csk_roll2_step2(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -271,6 +275,11 @@ def test_csk_roll2_step3(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.{tld}" + ) + @pytest.mark.parametrize( "tld", @@ -310,6 +319,8 @@ def test_csk_roll2_step4(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -374,6 +385,8 @@ def test_csk_roll2_step5(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -409,6 +422,8 @@ def test_csk_roll2_step6(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -439,3 +454,5 @@ def test_csk_roll2_step7(tld, alg, size, ns3): "verbose": True, } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py b/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py index 23d47bdbb6..818df48f7d 100644 --- a/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py +++ b/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py @@ -122,6 +122,8 @@ def test_rollover_enable_dnssec_step1(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -154,6 +156,8 @@ def test_rollover_enable_dnssec_step2(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -208,6 +212,11 @@ def test_rollover_enable_dnssec_step3(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.{tld}" + ) + @pytest.mark.parametrize( "tld", @@ -237,3 +246,5 @@ def test_rollover_enable_dnssec_step4(tld, alg, size, ns3): "nextev": TIMEDELTA["PT1H"], } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py b/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py index 92950124ed..fc83ae5beb 100644 --- a/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py +++ b/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py @@ -70,3 +70,5 @@ def test_going_insecure_initial(zone, ns3, alg, size): "nextev": None, } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py b/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py index 5cd8d65816..485d70d434 100644 --- a/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py +++ b/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py @@ -63,14 +63,14 @@ def after_servers_start(ns3, templates): def test_going_insecure_reconfig_step1(zone, alg, size, ns3): config = DEFAULT_CONFIG policy = "insecure" - zone = f"step1.{zone}" + szone = f"step1.{zone}" - isctest.kasp.wait_keymgr_done(ns3, zone, reconfig=True) + isctest.kasp.wait_keymgr_done(ns3, szone, reconfig=True) # Key goal states should be HIDDEN. # The DS may be removed if we are going insecure. step = { - "zone": zone, + "zone": szone, "cdss": CDSS, "keyprops": [ f"ksk 0 {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{-DURATION['P10D']}", @@ -85,6 +85,16 @@ def test_going_insecure_reconfig_step1(zone, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, config, policy, step) + with ns3.watch_log_from_start() as watcher: + if "dynamic" in zone: + watcher.wait_for_line( + f"zone {szone}/IN: dsyncfetch: send NOTIFY(CDS) query to scanner.kasp" + ) + else: + watcher.wait_for_line( + f"zone {szone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.kasp" + ) + @pytest.mark.parametrize( "zone", @@ -119,3 +129,5 @@ def test_going_insecure_reconfig_step2(zone, alg, size, ns3): "check-keytimes": False, } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py b/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py index 8ced405fbc..401324d9fb 100644 --- a/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py +++ b/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py @@ -115,3 +115,8 @@ def test_rollover_ksk_three_is_a_crowd(alg, size, ns3): expected[1].timing["Removed"] = now + KSK_IPUB + KSK_IRET isctest.kasp.check_keytimes(keys, expected) + + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.kasp" + ) diff --git a/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py b/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py index 0eaa236b6d..8ef59716a3 100644 --- a/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py +++ b/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py @@ -107,6 +107,8 @@ def test_ksk_doubleksk_step1(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, KSK_CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -166,6 +168,8 @@ def test_ksk_doubleksk_step2(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, KSK_CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -246,6 +250,11 @@ def test_ksk_doubleksk_step3(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, KSK_CONFIG, policy, step) + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.{tld}" + ) + @pytest.mark.parametrize( "tld", @@ -312,6 +321,8 @@ def test_ksk_doubleksk_step4(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, KSK_CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -349,6 +360,8 @@ def test_ksk_doubleksk_step5(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, KSK_CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -376,3 +389,5 @@ def test_ksk_doubleksk_step6(tld, alg, size, ns3): "nextev": None, } isctest.kasp.check_rollover_step(ns3, KSK_CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py b/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py index a7f5ead0fa..8f24c13b91 100644 --- a/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py +++ b/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py @@ -48,3 +48,5 @@ def test_lifetime_initial(zone, policy, lifetime, alg, size, ns3): "nextev": None, } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py b/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py index d28a7e4cd5..77529993c6 100644 --- a/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py +++ b/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py @@ -63,3 +63,5 @@ def test_lifetime_reconfig(zone, policy, lifetime, alg, size, ns3): "nextev": None, } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py b/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py index c38e5ddd49..7a364c3149 100644 --- a/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py +++ b/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py @@ -68,3 +68,5 @@ def test_straight2none_initial(zone, ns3, alg, size): "nextev": None, } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py b/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py index 5cfb49653b..d54d98d65a 100644 --- a/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py +++ b/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py @@ -77,3 +77,5 @@ def test_straight2none_reconfig(zone, ns3, alg, size): "nextev": None, } isctest.kasp.check_rollover_step(ns3, config, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py b/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py index 2720275c66..9a93911e18 100644 --- a/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py +++ b/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py @@ -116,6 +116,8 @@ def test_zsk_prepub_step1(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -173,6 +175,8 @@ def test_zsk_prepub_step2(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -251,6 +255,8 @@ def test_zsk_prepub_step3(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + # Force full resign and check all signatures have been replaced. with ns3.watch_log_from_here() as watcher: ns3.rndc(f"sign {zone}") @@ -321,6 +327,8 @@ def test_zsk_prepub_step4(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -356,6 +364,8 @@ def test_zsk_prepub_step5(tld, alg, size, ns3): } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log + @pytest.mark.parametrize( "tld", @@ -382,3 +392,5 @@ def test_zsk_prepub_step6(tld, alg, size, ns3): "nextev": None, } isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) + + assert f"zone {zone}/IN (signed): dsyncfetch" not in ns3.log diff --git a/bin/tests/system/rollover/ns3/named.common.conf.j2 b/bin/tests/system/rollover/ns3/named.common.conf.j2 index 813aa919e1..2c64dbeaa3 100644 --- a/bin/tests/system/rollover/ns3/named.common.conf.j2 +++ b/bin/tests/system/rollover/ns3/named.common.conf.j2 @@ -30,6 +30,7 @@ options { allow-transfer { any; }; recursion yes; dnssec-validation @dnssec_validation@; + notify-cds yes; }; key rndc_key { From e69eb0528a8fa301bf12fc5832693ef55328234c Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Fri, 12 Dec 2025 15:49:19 +0100 Subject: [PATCH 13/13] Test invalid DSYNC RRset is rejected The RFC says There MUST NOT be more than one DSYNC record for each combination of RRtype and Scheme. If we encounter more we should drop the response, as the DSYNC RRset is invalid. --- bin/tests/system/multisigner/ns1/root.db.in | 3 ++ bin/tests/system/multisigner/ns1/setup.sh | 2 +- .../system/multisigner/ns2/bad-dsync.db.in.j2 | 35 +++++++++++++++++++ .../system/multisigner/ns2/named.conf.j2 | 5 +++ bin/tests/system/multisigner/ns2/setup.sh | 1 + .../multisigner/ns3/model2.bad-dsync.db | 26 ++++++++++++++ .../system/multisigner/ns3/named.conf.j2 | 8 +++++ bin/tests/system/multisigner/ns3/setup.sh | 9 +++++ .../multisigner/ns4/model2.bad-dsync.db | 26 ++++++++++++++ .../system/multisigner/ns4/named.conf.j2 | 8 +++++ bin/tests/system/multisigner/ns4/setup.sh | 9 +++++ .../system/multisigner/tests_multisigner.py | 19 ++++++++++ 12 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 bin/tests/system/multisigner/ns2/bad-dsync.db.in.j2 create mode 100644 bin/tests/system/multisigner/ns3/model2.bad-dsync.db create mode 100644 bin/tests/system/multisigner/ns4/model2.bad-dsync.db diff --git a/bin/tests/system/multisigner/ns1/root.db.in b/bin/tests/system/multisigner/ns1/root.db.in index 395e28089e..e96ade64a8 100644 --- a/bin/tests/system/multisigner/ns1/root.db.in +++ b/bin/tests/system/multisigner/ns1/root.db.in @@ -23,5 +23,8 @@ a.root-servers.nil. A 10.53.0.1 multisigner. NS ns2.multisigner. ns2.multisigner. A 10.53.0.2 +bad-dsync. NS ns2.bad-dsync. +ns2.bad-dsync. A 10.53.0.2 + secondary. NS ns2.secondary. ns2.secondary. A 10.53.0.2 diff --git a/bin/tests/system/multisigner/ns1/setup.sh b/bin/tests/system/multisigner/ns1/setup.sh index 293f6ff192..b6cf3028d1 100644 --- a/bin/tests/system/multisigner/ns1/setup.sh +++ b/bin/tests/system/multisigner/ns1/setup.sh @@ -22,7 +22,7 @@ zonefile=root.db echo_i "ns1/setup.sh" -for tld in multisigner secondary; do +for tld in multisigner bad-dsync secondary; do cp "../ns2/dsset-${tld}." . done diff --git a/bin/tests/system/multisigner/ns2/bad-dsync.db.in.j2 b/bin/tests/system/multisigner/ns2/bad-dsync.db.in.j2 new file mode 100644 index 0000000000..b84eb5afec --- /dev/null +++ b/bin/tests/system/multisigner/ns2/bad-dsync.db.in.j2 @@ -0,0 +1,35 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +$ORIGIN bad-dsync. + +bad-dsync. IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + +scanner A 10.53.0.2 + +model2 NS ns3 + NS ns4 + +ns3.model2 A 10.53.0.3 +ns4.model2 A 10.53.0.4 + +*._dsync DSYNC CDS NOTIFY @PORT@ scanner1 +*._dsync DSYNC CDS NOTIFY @PORT@ scanner2 diff --git a/bin/tests/system/multisigner/ns2/named.conf.j2 b/bin/tests/system/multisigner/ns2/named.conf.j2 index 951c840d49..f2ef9302af 100644 --- a/bin/tests/system/multisigner/ns2/named.conf.j2 +++ b/bin/tests/system/multisigner/ns2/named.conf.j2 @@ -40,6 +40,11 @@ zone "multisigner" { file "multisigner.db.signed"; }; +zone "bad-dsync" { + type primary; + file "bad-dsync.db.signed"; +}; + zone "secondary" { type primary; file "secondary.db.signed"; diff --git a/bin/tests/system/multisigner/ns2/setup.sh b/bin/tests/system/multisigner/ns2/setup.sh index 025c5d3204..4fd349125d 100644 --- a/bin/tests/system/multisigner/ns2/setup.sh +++ b/bin/tests/system/multisigner/ns2/setup.sh @@ -35,4 +35,5 @@ setup() { } setup "multisigner" +setup "bad-dsync" setup "secondary" diff --git a/bin/tests/system/multisigner/ns3/model2.bad-dsync.db b/bin/tests/system/multisigner/ns3/model2.bad-dsync.db new file mode 100644 index 0000000000..5850e016b9 --- /dev/null +++ b/bin/tests/system/multisigner/ns3/model2.bad-dsync.db @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 diff --git a/bin/tests/system/multisigner/ns3/named.conf.j2 b/bin/tests/system/multisigner/ns3/named.conf.j2 index fc51c882f4..b56f01bf27 100644 --- a/bin/tests/system/multisigner/ns3/named.conf.j2 +++ b/bin/tests/system/multisigner/ns3/named.conf.j2 @@ -46,6 +46,14 @@ zone "model2.multisigner." { inline-signing no; }; +zone "model2.bad-dsync." { + type primary; + allow-update { any; }; + file "model2.bad-dsync.db"; + dnssec-policy model2; + inline-signing no; +}; + zone "model2.secondary." { type secondary; primaries { 10.53.0.5; }; diff --git a/bin/tests/system/multisigner/ns3/setup.sh b/bin/tests/system/multisigner/ns3/setup.sh index 2194f4e8d8..50f26480f7 100644 --- a/bin/tests/system/multisigner/ns3/setup.sh +++ b/bin/tests/system/multisigner/ns3/setup.sh @@ -29,6 +29,15 @@ $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 $DSFROMKEY $KSK.key >dsset-ns3-${zone}. +zone="model2.bad-dsync" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) +$SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns3-${zone}. + zone="model2.secondary" echo_i "setting up zone: $zone" zonefile="${zone}.db" diff --git a/bin/tests/system/multisigner/ns4/model2.bad-dsync.db b/bin/tests/system/multisigner/ns4/model2.bad-dsync.db new file mode 100644 index 0000000000..86a1708b45 --- /dev/null +++ b/bin/tests/system/multisigner/ns4/model2.bad-dsync.db @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns4 +ns4 A 10.53.0.4 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 diff --git a/bin/tests/system/multisigner/ns4/named.conf.j2 b/bin/tests/system/multisigner/ns4/named.conf.j2 index 017dd6c50a..3d551d04c4 100644 --- a/bin/tests/system/multisigner/ns4/named.conf.j2 +++ b/bin/tests/system/multisigner/ns4/named.conf.j2 @@ -46,6 +46,14 @@ zone "model2.multisigner." { inline-signing yes; }; +zone "model2.bad-dsync." { + type primary; + allow-update { any; }; + file "model2.bad-dsync.db"; + dnssec-policy model2; + inline-signing yes; +}; + zone "model2.secondary." { type secondary; primaries { 10.53.0.5; }; diff --git a/bin/tests/system/multisigner/ns4/setup.sh b/bin/tests/system/multisigner/ns4/setup.sh index d50d3bd26c..bb13b8bede 100644 --- a/bin/tests/system/multisigner/ns4/setup.sh +++ b/bin/tests/system/multisigner/ns4/setup.sh @@ -29,6 +29,15 @@ $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 $DSFROMKEY $KSK.key >dsset-ns4-${zone}. +zone="model2.bad-dsync" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) +$SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns4-${zone}. + zone="model2.secondary" echo_i "setting up zone: $zone" zonefile="${zone}.db" diff --git a/bin/tests/system/multisigner/tests_multisigner.py b/bin/tests/system/multisigner/tests_multisigner.py index bc53c24975..15726f1eb1 100644 --- a/bin/tests/system/multisigner/tests_multisigner.py +++ b/bin/tests/system/multisigner/tests_multisigner.py @@ -595,6 +595,25 @@ def test_multisigner(ns2, ns3, ns4): check_no_dnssec_in_journal(ns4, zone) +def test_multisigner_bad_dsync(ns3, ns4): + zone = "model2.bad-dsync" + + # First make sure the zone is properly signed. + isctest.log.info(f"basic DNSSEC tests for {zone}") + isctest.kasp.wait_keymgr_done(ns3, zone) + isctest.kasp.wait_keymgr_done(ns4, zone) + + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN: dsyncfetch: multiple DSYNC records matching NOTIFY scheme and CDS RRtype, dropping response" + ) + + with ns4.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: multiple DSYNC records matching NOTIFY scheme and CDS RRtype, dropping response" + ) + + def test_multisigner_secondary(ns2, ns3, ns4, ns5): zone = "model2.secondary" keyprops = [