Add checkds code
Similar to notify, add code to send and keep track of checkds requests. On every zone_rekey event, we will check the DS at parental agents (but we will only actually query parental agents if theree is a DS scheduled to be published/withdrawn). On a zone_rekey event, we will first clear the ongoing checkds requests. Reset the counter, to avoid continuing KSK rollover premature. This has the risk that if zone_rekey events happen too soon after each other, there are redundant DS queries to the parental agents. But if TTLs and the configured durations in the dnssec-policy are sane (as in not ridiculous short) the chance of this happening is low.
This commit is contained in:
@@ -80,6 +80,7 @@
|
||||
#define DNS_EVENT_STARTUPDATE (ISC_EVENTCLASS_DNS + 58)
|
||||
#define DNS_EVENT_TRYSTALE (ISC_EVENTCLASS_DNS + 59)
|
||||
#define DNS_EVENT_ZONEFLUSH (ISC_EVENTCLASS_DNS + 60)
|
||||
#define DNS_EVENT_CHECKDSSENDTOADDR (ISC_EVENTCLASS_DNS + 61)
|
||||
|
||||
#define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0)
|
||||
#define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535)
|
||||
|
||||
+856
-4
@@ -93,6 +93,9 @@
|
||||
#define NOTIFY_MAGIC ISC_MAGIC('N', 't', 'f', 'y')
|
||||
#define DNS_NOTIFY_VALID(notify) ISC_MAGIC_VALID(notify, NOTIFY_MAGIC)
|
||||
|
||||
#define CHECKDS_MAGIC ISC_MAGIC('C', 'h', 'D', 'S')
|
||||
#define DNS_CHECKDS_VALID(checkds) ISC_MAGIC_VALID(checkds, CHECKDS_MAGIC)
|
||||
|
||||
#define STUB_MAGIC ISC_MAGIC('S', 't', 'u', 'b')
|
||||
#define DNS_STUB_VALID(stub) ISC_MAGIC_VALID(stub, STUB_MAGIC)
|
||||
|
||||
@@ -153,6 +156,7 @@
|
||||
#endif /* ifndef DNS_DUMP_DELAY */
|
||||
|
||||
typedef struct dns_notify dns_notify_t;
|
||||
typedef struct dns_checkds dns_checkds_t;
|
||||
typedef struct dns_stub dns_stub_t;
|
||||
typedef struct dns_load dns_load_t;
|
||||
typedef struct dns_forward dns_forward_t;
|
||||
@@ -330,6 +334,7 @@ struct dns_zone {
|
||||
bool zero_no_soa_ttl;
|
||||
dns_severity_t check_names;
|
||||
ISC_LIST(dns_notify_t) notifies;
|
||||
ISC_LIST(dns_checkds_t) checkds_requests;
|
||||
dns_request_t *request;
|
||||
dns_loadctx_t *lctx;
|
||||
dns_io_t *readio;
|
||||
@@ -646,6 +651,23 @@ struct dns_notify {
|
||||
#define DNS_NOTIFY_NOSOA 0x0001U
|
||||
#define DNS_NOTIFY_STARTUP 0x0002U
|
||||
|
||||
/*%
|
||||
* Hold checkds state.
|
||||
*/
|
||||
struct dns_checkds {
|
||||
unsigned int magic;
|
||||
unsigned int flags;
|
||||
isc_mem_t *mctx;
|
||||
dns_zone_t *zone;
|
||||
dns_request_t *request;
|
||||
isc_sockaddr_t dst;
|
||||
dns_tsigkey_t *key;
|
||||
dns_transport_t *transport;
|
||||
isc_dscp_t dscp;
|
||||
ISC_LINK(dns_checkds_t) link;
|
||||
isc_event_t *event;
|
||||
};
|
||||
|
||||
/*%
|
||||
* dns_stub holds state while performing a 'stub' transfer.
|
||||
* 'db' is the zone's 'db' or a new one if this is the initial
|
||||
@@ -867,6 +889,16 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub);
|
||||
static int
|
||||
message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type);
|
||||
static void
|
||||
checkds_cancel(dns_zone_t *zone);
|
||||
static void
|
||||
checkds_send(dns_zone_t *zone);
|
||||
static isc_result_t
|
||||
checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep);
|
||||
static void
|
||||
checkds_done(isc_task_t *task, isc_event_t *event);
|
||||
static void
|
||||
checkds_send_toaddr(isc_task_t *task, isc_event_t *event);
|
||||
static void
|
||||
notify_cancel(dns_zone_t *zone);
|
||||
static void
|
||||
notify_find_address(dns_notify_t *notify);
|
||||
@@ -1106,6 +1138,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) {
|
||||
isc_time_settoepoch(&zone->nsec3chaintime);
|
||||
isc_time_settoepoch(&zone->refreshkeytime);
|
||||
ISC_LIST_INIT(zone->notifies);
|
||||
ISC_LIST_INIT(zone->checkds_requests);
|
||||
isc_sockaddr_any(&zone->notifysrc4);
|
||||
isc_sockaddr_any6(&zone->notifysrc6);
|
||||
isc_sockaddr_any(&zone->parentalsrc4);
|
||||
@@ -5957,7 +5990,6 @@ dns_zone_getaltxfrsource6dscp(dns_zone_t *zone) {
|
||||
return (zone->altxfrsource6dscp);
|
||||
}
|
||||
|
||||
|
||||
isc_result_t
|
||||
dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) {
|
||||
REQUIRE(DNS_ZONE_VALID(zone));
|
||||
@@ -12141,6 +12173,25 @@ notify_cancel(dns_zone_t *zone) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
checkds_cancel(dns_zone_t *zone) {
|
||||
dns_checkds_t *checkds;
|
||||
|
||||
/*
|
||||
* 'zone' locked by caller.
|
||||
*/
|
||||
|
||||
REQUIRE(LOCKED_ZONE(zone));
|
||||
|
||||
for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL;
|
||||
checkds = ISC_LIST_NEXT(checkds, link))
|
||||
{
|
||||
if (checkds->request != NULL) {
|
||||
dns_request_cancel(checkds->request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
forward_cancel(dns_zone_t *zone) {
|
||||
dns_forward_t *forward;
|
||||
@@ -14940,6 +14991,8 @@ zone_shutdown(isc_task_t *task, isc_event_t *event) {
|
||||
}
|
||||
}
|
||||
|
||||
checkds_cancel(zone);
|
||||
|
||||
notify_cancel(zone);
|
||||
|
||||
forward_cancel(zone);
|
||||
@@ -20284,6 +20337,781 @@ dnssec_report(const char *format, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static void
|
||||
checkds_destroy(dns_checkds_t *checkds, bool locked) {
|
||||
isc_mem_t *mctx;
|
||||
|
||||
REQUIRE(DNS_CHECKDS_VALID(checkds));
|
||||
|
||||
dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: destroy DS query");
|
||||
|
||||
if (checkds->zone != NULL) {
|
||||
if (!locked) {
|
||||
LOCK_ZONE(checkds->zone);
|
||||
}
|
||||
REQUIRE(LOCKED_ZONE(checkds->zone));
|
||||
if (ISC_LINK_LINKED(checkds, link)) {
|
||||
ISC_LIST_UNLINK(checkds->zone->checkds_requests,
|
||||
checkds, link);
|
||||
}
|
||||
if (!locked) {
|
||||
UNLOCK_ZONE(checkds->zone);
|
||||
}
|
||||
if (locked) {
|
||||
zone_idetach(&checkds->zone);
|
||||
} else {
|
||||
dns_zone_idetach(&checkds->zone);
|
||||
}
|
||||
}
|
||||
if (checkds->request != NULL) {
|
||||
dns_request_destroy(&checkds->request);
|
||||
}
|
||||
if (checkds->key != NULL) {
|
||||
dns_tsigkey_detach(&checkds->key);
|
||||
}
|
||||
if (checkds->transport != NULL) {
|
||||
dns_transport_detach(&checkds->transport);
|
||||
}
|
||||
mctx = checkds->mctx;
|
||||
isc_mem_put(checkds->mctx, checkds, sizeof(*checkds));
|
||||
isc_mem_detach(&mctx);
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize,
|
||||
dns_rdata_t *target) {
|
||||
isc_result_t result;
|
||||
isc_buffer_t b;
|
||||
isc_region_t r;
|
||||
|
||||
isc_buffer_init(&b, buf, bufsize);
|
||||
result = dst_key_todns(key, &b);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
return (result);
|
||||
}
|
||||
|
||||
dns_rdata_reset(target);
|
||||
isc_buffer_usedregion(&b, &r);
|
||||
dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey,
|
||||
&r);
|
||||
return (ISC_R_SUCCESS);
|
||||
}
|
||||
|
||||
static bool
|
||||
do_checkds(dns_zone_t *zone, dst_key_t *key, isc_stdtime_t now,
|
||||
bool dspublish) {
|
||||
dns_kasp_t *kasp = dns_zone_getkasp(zone);
|
||||
const char *dir = dns_zone_getkeydirectory(zone);
|
||||
isc_result_t result;
|
||||
uint32_t count = 0;
|
||||
|
||||
if (dspublish) {
|
||||
(void)dst_key_getnum(key, DST_NUM_DSPUBCOUNT, &count);
|
||||
count += 1;
|
||||
dst_key_setnum(key, DST_NUM_DSPUBCOUNT, count);
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: %u DS published "
|
||||
"for key %u",
|
||||
count, dst_key_id(key));
|
||||
|
||||
if (count != zone->parentalscnt) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
(void)dst_key_getnum(key, DST_NUM_DSDELCOUNT, &count);
|
||||
count += 1;
|
||||
dst_key_setnum(key, DST_NUM_DSDELCOUNT, count);
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: %u DS withdrawn "
|
||||
"for key %u",
|
||||
count, dst_key_id(key));
|
||||
|
||||
if (count != zone->parentalscnt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: checkds %s for key "
|
||||
"%u",
|
||||
dspublish ? "published" : "withdrawn", dst_key_id(key));
|
||||
|
||||
dns_zone_lock_keyfiles(zone);
|
||||
result = dns_keymgr_checkds_id(kasp, &zone->checkds_ok, dir, now, now,
|
||||
dspublish, dst_key_id(key),
|
||||
dst_key_alg(key));
|
||||
dns_zone_unlock_keyfiles(zone);
|
||||
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dns_zone_log(zone, ISC_LOG_WARNING,
|
||||
"checkds: checkds for key %u failed: %s",
|
||||
dst_key_id(key), isc_result_totext(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
validate_ds(dns_zone_t *zone, dns_message_t *message) {
|
||||
UNUSED(zone);
|
||||
UNUSED(message);
|
||||
|
||||
/* Get closest trust anchor */
|
||||
|
||||
/* Check that trust anchor is (grand)parent of zone. */
|
||||
|
||||
/* Find the DNSKEY signing the message. */
|
||||
|
||||
/* Check that DNSKEY is in chain of trust. */
|
||||
|
||||
/* Validate DS RRset. */
|
||||
|
||||
return (ISC_R_SUCCESS);
|
||||
}
|
||||
|
||||
static void
|
||||
checkds_done(isc_task_t *task, isc_event_t *event) {
|
||||
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
||||
char rcode[128];
|
||||
dns_checkds_t *checkds;
|
||||
dns_zone_t *zone;
|
||||
dns_db_t *db = NULL;
|
||||
dns_dbversion_t *version = NULL;
|
||||
dns_dnsseckey_t *key;
|
||||
dns_dnsseckeylist_t keys;
|
||||
dns_kasp_t *kasp = NULL;
|
||||
dns_message_t *message = NULL;
|
||||
dns_rdataset_t *ds_rrset = NULL;
|
||||
dns_requestevent_t *revent = (dns_requestevent_t *)event;
|
||||
isc_buffer_t buf;
|
||||
isc_result_t result;
|
||||
isc_stdtime_t now;
|
||||
isc_time_t timenow;
|
||||
bool rekey = false;
|
||||
bool empty = false;
|
||||
|
||||
UNUSED(task);
|
||||
|
||||
checkds = event->ev_arg;
|
||||
REQUIRE(DNS_CHECKDS_VALID(checkds));
|
||||
|
||||
zone = checkds->zone;
|
||||
INSIST(task == zone->task);
|
||||
|
||||
ISC_LIST_INIT(keys);
|
||||
|
||||
kasp = zone->kasp;
|
||||
INSIST(kasp != NULL);
|
||||
|
||||
isc_buffer_init(&buf, rcode, sizeof(rcode));
|
||||
isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf));
|
||||
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(1), "checkds: DS query to %s: done",
|
||||
addrbuf);
|
||||
|
||||
dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &message);
|
||||
INSIST(message != NULL);
|
||||
|
||||
CHECK(revent->result);
|
||||
CHECK(dns_request_getresponse(revent->request, message,
|
||||
DNS_MESSAGEPARSE_PRESERVEORDER));
|
||||
CHECK(dns_rcode_totext(message->rcode, &buf));
|
||||
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: DS response from %s: %.*s", addrbuf,
|
||||
(int)buf.used, rcode);
|
||||
|
||||
/* Validate response. */
|
||||
CHECK(validate_ds(zone, message));
|
||||
|
||||
if (message->rcode != dns_rcode_noerror) {
|
||||
dns_zone_log(zone, ISC_LOG_NOTICE,
|
||||
"checkds: bad DS response from %s: %.*s", addrbuf,
|
||||
(int)buf.used, rcode);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/* Lookup DS RRset. */
|
||||
result = dns_message_firstname(message, DNS_SECTION_ANSWER);
|
||||
while (result == ISC_R_SUCCESS) {
|
||||
dns_name_t *name = NULL;
|
||||
dns_rdataset_t *rdataset;
|
||||
|
||||
dns_message_currentname(message, DNS_SECTION_ANSWER, &name);
|
||||
if (dns_name_compare(&zone->origin, name) != 0) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
|
||||
rdataset = ISC_LIST_NEXT(rdataset, link))
|
||||
{
|
||||
if (rdataset->type != dns_rdatatype_ds) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ds_rrset = rdataset;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ds_rrset != NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
next:
|
||||
result = dns_message_nextname(message, DNS_SECTION_ANSWER);
|
||||
}
|
||||
|
||||
if (ds_rrset == NULL) {
|
||||
empty = true;
|
||||
dns_zone_log(zone, ISC_LOG_NOTICE,
|
||||
"checkds: empty DS response from %s", addrbuf);
|
||||
}
|
||||
|
||||
TIME_NOW(&timenow);
|
||||
now = isc_time_seconds(&timenow);
|
||||
|
||||
CHECK(dns_zone_getdb(zone, &db));
|
||||
dns_db_currentversion(db, &version);
|
||||
|
||||
KASP_LOCK(kasp);
|
||||
LOCK_ZONE(zone);
|
||||
for (key = ISC_LIST_HEAD(zone->checkds_ok); key != NULL;
|
||||
key = ISC_LIST_NEXT(key, link))
|
||||
{
|
||||
bool alldone = false, found = false;
|
||||
bool checkdspub = false, checkdsdel = false, ksk = false;
|
||||
dst_key_state_t ds_state = DST_KEY_STATE_NA;
|
||||
isc_stdtime_t published = 0, withdrawn = 0;
|
||||
isc_result_t ret = ISC_R_SUCCESS;
|
||||
|
||||
/* Is this key have the KSK role? */
|
||||
(void)dst_key_role(key->key, &ksk, NULL);
|
||||
if (!ksk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Do we need to check the DS RRset for this key? */
|
||||
(void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state);
|
||||
(void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published);
|
||||
(void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn);
|
||||
|
||||
if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) {
|
||||
checkdspub = true;
|
||||
} else if (ds_state == DST_KEY_STATE_UNRETENTIVE &&
|
||||
withdrawn == 0) {
|
||||
checkdsdel = true;
|
||||
}
|
||||
if (!checkdspub && !checkdsdel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
goto dswithdrawn;
|
||||
}
|
||||
|
||||
/* Find the appropriate DS record. */
|
||||
ret = dns_rdataset_first(ds_rrset);
|
||||
while (ret == ISC_R_SUCCESS) {
|
||||
dns_rdata_ds_t ds;
|
||||
dns_rdata_t dnskey = DNS_RDATA_INIT;
|
||||
dns_rdata_t dsrdata = DNS_RDATA_INIT;
|
||||
dns_rdata_t rdata = DNS_RDATA_INIT;
|
||||
isc_result_t r;
|
||||
unsigned char dsbuf[DNS_DS_BUFFERSIZE];
|
||||
unsigned char keybuf[DST_KEY_MAXSIZE];
|
||||
|
||||
dns_rdataset_current(ds_rrset, &rdata);
|
||||
r = dns_rdata_tostruct(&rdata, &ds, NULL);
|
||||
if (r != ISC_R_SUCCESS) {
|
||||
goto nextds;
|
||||
}
|
||||
/* Check key tag and algorithm. */
|
||||
if (dst_key_id(key->key) != ds.key_tag) {
|
||||
goto nextds;
|
||||
}
|
||||
if (dst_key_alg(key->key) != ds.algorithm) {
|
||||
goto nextds;
|
||||
}
|
||||
/* Derive DS from DNSKEY, see if the rdata is equal. */
|
||||
make_dnskey(key->key, keybuf, sizeof(keybuf), &dnskey);
|
||||
r = dns_ds_buildrdata(&zone->origin, &dnskey,
|
||||
ds.digest_type, dsbuf, &dsrdata);
|
||||
if (r != ISC_R_SUCCESS) {
|
||||
goto nextds;
|
||||
}
|
||||
if (dns_rdata_compare(&rdata, &dsrdata) == 0) {
|
||||
found = true;
|
||||
if (checkdspub) {
|
||||
/* DS Published. */
|
||||
alldone = do_checkds(zone, key->key,
|
||||
now, true);
|
||||
if (alldone) {
|
||||
rekey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextds:
|
||||
ret = dns_rdataset_next(ds_rrset);
|
||||
}
|
||||
|
||||
dswithdrawn:
|
||||
/* DS withdrawn. */
|
||||
if (checkdsdel && !found) {
|
||||
alldone = do_checkds(zone, key->key, now, false);
|
||||
if (alldone) {
|
||||
rekey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
UNLOCK_ZONE(zone);
|
||||
KASP_UNLOCK(kasp);
|
||||
|
||||
/* Rekey after checkds. */
|
||||
if (rekey) {
|
||||
dns_zone_rekey(zone, false);
|
||||
}
|
||||
|
||||
failure:
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: DS request failed: %s",
|
||||
isc_result_totext(result));
|
||||
}
|
||||
|
||||
if (version != NULL) {
|
||||
dns_db_closeversion(db, &version, false);
|
||||
}
|
||||
if (db != NULL) {
|
||||
dns_db_detach(&db);
|
||||
}
|
||||
|
||||
while (!ISC_LIST_EMPTY(keys)) {
|
||||
key = ISC_LIST_HEAD(keys);
|
||||
ISC_LIST_UNLINK(keys, key, link);
|
||||
dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key);
|
||||
}
|
||||
|
||||
isc_event_free(&event);
|
||||
checkds_destroy(checkds, false);
|
||||
dns_message_detach(&message);
|
||||
}
|
||||
|
||||
static bool
|
||||
checkds_isqueued(dns_zone_t *zone, isc_sockaddr_t *addr, dns_tsigkey_t *key,
|
||||
dns_transport_t *transport) {
|
||||
dns_checkds_t *checkds;
|
||||
|
||||
for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL;
|
||||
checkds = ISC_LIST_NEXT(checkds, link))
|
||||
{
|
||||
if (checkds->request != NULL) {
|
||||
continue;
|
||||
}
|
||||
if (addr != NULL && isc_sockaddr_equal(addr, &checkds->dst) &&
|
||||
checkds->key == key && checkds->transport == transport)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
checkds_create(isc_mem_t *mctx, unsigned int flags, dns_checkds_t **checkdsp) {
|
||||
dns_checkds_t *checkds;
|
||||
|
||||
REQUIRE(checkdsp != NULL && *checkdsp == NULL);
|
||||
|
||||
checkds = isc_mem_get(mctx, sizeof(*checkds));
|
||||
*checkds = (dns_checkds_t){
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
isc_mem_attach(mctx, &checkds->mctx);
|
||||
isc_sockaddr_any(&checkds->dst);
|
||||
ISC_LINK_INIT(checkds, link);
|
||||
checkds->magic = CHECKDS_MAGIC;
|
||||
*checkdsp = checkds;
|
||||
return (ISC_R_SUCCESS);
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep) {
|
||||
dns_message_t *message = NULL;
|
||||
|
||||
dns_name_t *tempname = NULL;
|
||||
dns_rdataset_t *temprdataset = NULL;
|
||||
|
||||
isc_result_t result;
|
||||
|
||||
REQUIRE(DNS_ZONE_VALID(zone));
|
||||
REQUIRE(messagep != NULL && *messagep == NULL);
|
||||
|
||||
dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message);
|
||||
|
||||
message->opcode = dns_opcode_query;
|
||||
message->rdclass = zone->rdclass;
|
||||
|
||||
result = dns_message_gettempname(message, &tempname);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = dns_message_gettemprdataset(message, &temprdataset);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make question.
|
||||
*/
|
||||
dns_name_init(tempname, NULL);
|
||||
dns_name_clone(&zone->origin, tempname);
|
||||
dns_rdataset_makequestion(temprdataset, zone->rdclass,
|
||||
dns_rdatatype_ds);
|
||||
ISC_LIST_APPEND(tempname->list, temprdataset, link);
|
||||
dns_message_addname(message, tempname, DNS_SECTION_QUESTION);
|
||||
tempname = NULL;
|
||||
temprdataset = NULL;
|
||||
|
||||
*messagep = message;
|
||||
return (ISC_R_SUCCESS);
|
||||
|
||||
cleanup:
|
||||
if (tempname != NULL) {
|
||||
dns_message_puttempname(message, &tempname);
|
||||
}
|
||||
if (temprdataset != NULL) {
|
||||
dns_message_puttemprdataset(message, &temprdataset);
|
||||
}
|
||||
dns_message_detach(&message);
|
||||
return (result);
|
||||
}
|
||||
|
||||
static void
|
||||
checkds_send_toaddr(isc_task_t *task, isc_event_t *event) {
|
||||
dns_checkds_t *checkds;
|
||||
isc_result_t result;
|
||||
dns_message_t *message = NULL;
|
||||
isc_netaddr_t dstip;
|
||||
dns_tsigkey_t *key = NULL;
|
||||
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
||||
isc_sockaddr_t src;
|
||||
unsigned int options, timeout;
|
||||
bool have_checkdssource = false;
|
||||
bool have_checkdsdscp = false;
|
||||
isc_dscp_t dscp = -1;
|
||||
|
||||
checkds = event->ev_arg;
|
||||
REQUIRE(DNS_CHECKDS_VALID(checkds));
|
||||
|
||||
UNUSED(task);
|
||||
|
||||
LOCK_ZONE(checkds->zone);
|
||||
|
||||
checkds->event = NULL;
|
||||
|
||||
if (DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_LOADED) == 0) {
|
||||
result = ISC_R_CANCELED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 ||
|
||||
DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_EXITING) ||
|
||||
checkds->zone->view->requestmgr == NULL ||
|
||||
checkds->zone->db == NULL)
|
||||
{
|
||||
result = ISC_R_CANCELED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* The raw IPv4 address should also exist. Don't send to the
|
||||
* mapped form.
|
||||
*/
|
||||
if (isc_sockaddr_pf(&checkds->dst) == PF_INET6 &&
|
||||
IN6_IS_ADDR_V4MAPPED(&checkds->dst.type.sin6.sin6_addr))
|
||||
{
|
||||
isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf));
|
||||
dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: ignoring IPv6 mapped IPV4 address: %s",
|
||||
addrbuf);
|
||||
result = ISC_R_CANCELED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = checkds_createmessage(checkds->zone, &message);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf));
|
||||
if (checkds->key != NULL) {
|
||||
/* Transfer ownership of key */
|
||||
key = checkds->key;
|
||||
checkds->key = NULL;
|
||||
} else {
|
||||
isc_netaddr_fromsockaddr(&dstip, &checkds->dst);
|
||||
result = dns_view_getpeertsig(checkds->zone->view, &dstip,
|
||||
&key);
|
||||
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
||||
dns_zone_log(checkds->zone, ISC_LOG_ERROR,
|
||||
"checkds: DS query to %s not sent. "
|
||||
"Peer TSIG key lookup failure.",
|
||||
addrbuf);
|
||||
goto cleanup_message;
|
||||
}
|
||||
}
|
||||
|
||||
if (key != NULL) {
|
||||
char namebuf[DNS_NAME_FORMATSIZE];
|
||||
|
||||
dns_name_format(&key->name, namebuf, sizeof(namebuf));
|
||||
dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: sending DS query to %s : TSIG (%s)",
|
||||
addrbuf, namebuf);
|
||||
} else {
|
||||
dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: sending DS query to %s", addrbuf);
|
||||
}
|
||||
options = 0;
|
||||
if (checkds->zone->view->peers != NULL) {
|
||||
dns_peer_t *peer = NULL;
|
||||
bool usetcp = false;
|
||||
result = dns_peerlist_peerbyaddr(checkds->zone->view->peers,
|
||||
&dstip, &peer);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
result = dns_peer_getquerysource(peer, &src);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
have_checkdssource = true;
|
||||
}
|
||||
dns_peer_getquerydscp(peer, &dscp);
|
||||
if (dscp != -1) {
|
||||
have_checkdsdscp = true;
|
||||
}
|
||||
result = dns_peer_getforcetcp(peer, &usetcp);
|
||||
if (result == ISC_R_SUCCESS && usetcp) {
|
||||
options |= DNS_FETCHOPT_TCP;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (isc_sockaddr_pf(&checkds->dst)) {
|
||||
case PF_INET:
|
||||
if (!have_checkdssource) {
|
||||
src = checkds->zone->parentalsrc4;
|
||||
}
|
||||
if (!have_checkdsdscp) {
|
||||
dscp = checkds->zone->parentalsrc4dscp;
|
||||
}
|
||||
break;
|
||||
case PF_INET6:
|
||||
if (!have_checkdssource) {
|
||||
src = checkds->zone->parentalsrc6;
|
||||
}
|
||||
if (!have_checkdsdscp) {
|
||||
dscp = checkds->zone->parentalsrc6dscp;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = ISC_R_NOTIMPLEMENTED;
|
||||
goto cleanup_key;
|
||||
}
|
||||
|
||||
dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: create request for DS query to %s", addrbuf);
|
||||
|
||||
timeout = 15;
|
||||
options |= DNS_REQUESTOPT_TCP;
|
||||
result = dns_request_createvia(
|
||||
checkds->zone->view->requestmgr, message, &src, &checkds->dst,
|
||||
dscp, options, key, timeout * 3, timeout, 0,
|
||||
checkds->zone->task, checkds_done, checkds, &checkds->request);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dns_zone_log(
|
||||
checkds->zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: dns_request_createvia() to %s failed: %s",
|
||||
addrbuf, dns_result_totext(result));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup_key:
|
||||
if (key != NULL) {
|
||||
dns_tsigkey_detach(&key);
|
||||
}
|
||||
cleanup_message:
|
||||
dns_message_detach(&message);
|
||||
cleanup:
|
||||
UNLOCK_ZONE(checkds->zone);
|
||||
isc_event_free(&event);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
checkds_destroy(checkds, false);
|
||||
}
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
checkds_send_queue(dns_checkds_t *checkds) {
|
||||
isc_event_t *e;
|
||||
isc_result_t result;
|
||||
|
||||
INSIST(checkds->event == NULL);
|
||||
e = isc_event_allocate(checkds->mctx, NULL, DNS_EVENT_CHECKDSSENDTOADDR,
|
||||
checkds_send_toaddr, checkds,
|
||||
sizeof(isc_event_t));
|
||||
e->ev_arg = checkds;
|
||||
e->ev_sender = NULL;
|
||||
result = isc_ratelimiter_enqueue(checkds->zone->zmgr->checkdsrl,
|
||||
checkds->zone->task, &e);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_event_free(&e);
|
||||
checkds->event = NULL;
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
static void
|
||||
checkds_send(dns_zone_t *zone) {
|
||||
dns_view_t *view = dns_zone_getview(zone);
|
||||
isc_result_t result;
|
||||
unsigned int flags = 0;
|
||||
|
||||
/*
|
||||
* Zone lock held by caller.
|
||||
*/
|
||||
REQUIRE(LOCKED_ZONE(zone));
|
||||
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: start sending DS queries to %u parentals",
|
||||
zone->parentalscnt);
|
||||
|
||||
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: abort, named exiting");
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < zone->parentalscnt; i++) {
|
||||
dns_tsigkey_t *key = NULL;
|
||||
dns_transport_t *transport = NULL;
|
||||
isc_sockaddr_t dst;
|
||||
dns_checkds_t *checkds = NULL;
|
||||
|
||||
if ((zone->parentalkeynames != NULL) &&
|
||||
(zone->parentalkeynames[i] != NULL)) {
|
||||
dns_name_t *keyname = zone->parentalkeynames[i];
|
||||
(void)dns_view_gettsig(view, keyname, &key);
|
||||
}
|
||||
|
||||
if ((zone->parentaltlsnames != NULL) &&
|
||||
(zone->parentaltlsnames[i] != NULL)) {
|
||||
dns_name_t *tlsname = zone->parentaltlsnames[i];
|
||||
(void)dns_view_gettransport(view, DNS_TRANSPORT_TLS,
|
||||
tlsname, &transport);
|
||||
dns_zone_logc(
|
||||
zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR,
|
||||
"got TLS configuration for zone transfer");
|
||||
}
|
||||
|
||||
dst = zone->parentals[i];
|
||||
|
||||
/* TODO: glue the transport to the checkds request */
|
||||
|
||||
if (checkds_isqueued(zone, &dst, key, transport)) {
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: DS query to parent "
|
||||
"%d is queued",
|
||||
i);
|
||||
if (key != NULL) {
|
||||
dns_tsigkey_detach(&key);
|
||||
}
|
||||
if (transport != NULL) {
|
||||
dns_transport_detach(&transport);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: create DS query for "
|
||||
"parent %d",
|
||||
i);
|
||||
|
||||
result = checkds_create(zone->mctx, flags, &checkds);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: create DS query for "
|
||||
"parent %d failed",
|
||||
i);
|
||||
continue;
|
||||
}
|
||||
zone_iattach(zone, &checkds->zone);
|
||||
checkds->dst = dst;
|
||||
|
||||
INSIST(checkds->key == NULL);
|
||||
if (key != NULL) {
|
||||
checkds->key = key;
|
||||
key = NULL;
|
||||
}
|
||||
|
||||
INSIST(checkds->transport == NULL);
|
||||
if (transport != NULL) {
|
||||
checkds->transport = transport;
|
||||
transport = NULL;
|
||||
}
|
||||
|
||||
ISC_LIST_APPEND(zone->checkds_requests, checkds, link);
|
||||
result = checkds_send_queue(checkds);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dns_zone_log(zone, ISC_LOG_DEBUG(3),
|
||||
"checkds: send DS query to "
|
||||
"parent %d failed",
|
||||
i);
|
||||
checkds_destroy(checkds, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
zone_checkds(dns_zone_t *zone) {
|
||||
bool cdscheck = false;
|
||||
|
||||
for (dns_dnsseckey_t *key = ISC_LIST_HEAD(zone->checkds_ok);
|
||||
key != NULL; key = ISC_LIST_NEXT(key, link))
|
||||
{
|
||||
dst_key_state_t ds_state = DST_KEY_STATE_NA;
|
||||
bool ksk = false;
|
||||
isc_stdtime_t published = 0, withdrawn = 0;
|
||||
|
||||
/* Is this key have the KSK role? */
|
||||
(void)dst_key_role(key->key, &ksk, NULL);
|
||||
if (!ksk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Do we need to check the DS RRset? */
|
||||
(void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state);
|
||||
(void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published);
|
||||
(void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn);
|
||||
|
||||
if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) {
|
||||
dst_key_setnum(key->key, DST_NUM_DSPUBCOUNT, 0);
|
||||
cdscheck = true;
|
||||
} else if (ds_state == DST_KEY_STATE_UNRETENTIVE &&
|
||||
withdrawn == 0) {
|
||||
dst_key_setnum(key->key, DST_NUM_DSDELCOUNT, 0);
|
||||
cdscheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cdscheck) {
|
||||
/* Request the DS RRset. */
|
||||
LOCK_ZONE(zone);
|
||||
checkds_send(zone);
|
||||
UNLOCK_ZONE(zone);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
zone_rekey(dns_zone_t *zone) {
|
||||
isc_result_t result;
|
||||
@@ -20383,10 +21211,12 @@ zone_rekey(dns_zone_t *zone) {
|
||||
fullsign = DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN);
|
||||
|
||||
KASP_LOCK(kasp);
|
||||
dns_zone_lock_keyfiles(zone);
|
||||
|
||||
dns_zone_lock_keyfiles(zone);
|
||||
result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx,
|
||||
&keys);
|
||||
dns_zone_unlock_keyfiles(zone);
|
||||
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_DEBUG(1),
|
||||
"zone_rekey:dns_dnssec_findmatchingkeys failed: %s",
|
||||
@@ -20394,23 +21224,45 @@ zone_rekey(dns_zone_t *zone) {
|
||||
}
|
||||
|
||||
if (kasp != NULL) {
|
||||
/*
|
||||
* Check DS at parental agents. Clear ongoing checks.
|
||||
*/
|
||||
LOCK_ZONE(zone);
|
||||
checkds_cancel(zone);
|
||||
clear_keylist(&zone->checkds_ok, zone->mctx);
|
||||
ISC_LIST_INIT(zone->checkds_ok);
|
||||
UNLOCK_ZONE(zone);
|
||||
|
||||
result = dns_zone_getdnsseckeys(zone, db, ver, now,
|
||||
&zone->checkds_ok);
|
||||
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_rekey:dns_zone_getdnsseckeys failed: "
|
||||
"%s",
|
||||
isc_result_totext(result));
|
||||
} else {
|
||||
zone_checkds(zone);
|
||||
}
|
||||
|
||||
if (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND) {
|
||||
dns_zone_lock_keyfiles(zone);
|
||||
result = dns_keymgr_run(&zone->origin, zone->rdclass,
|
||||
dir, mctx, &keys, &dnskeys,
|
||||
kasp, now, &nexttime);
|
||||
dns_zone_unlock_keyfiles(zone);
|
||||
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
dnssec_log(zone, ISC_LOG_ERROR,
|
||||
"zone_rekey:dns_dnssec_keymgr "
|
||||
"failed: %s",
|
||||
isc_result_totext(result));
|
||||
dns_zone_unlock_keyfiles(zone);
|
||||
KASP_UNLOCK(kasp);
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dns_zone_unlock_keyfiles(zone);
|
||||
KASP_UNLOCK(kasp);
|
||||
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
|
||||
Reference in New Issue
Block a user