summaryrefslogtreecommitdiff
path: root/validator/validator.c
diff options
context:
space:
mode:
Diffstat (limited to 'validator/validator.c')
-rw-r--r--validator/validator.c499
1 files changed, 424 insertions, 75 deletions
diff --git a/validator/validator.c b/validator/validator.c
index 6cd15cfc1c7c..26d33a37ff31 100644
--- a/validator/validator.c
+++ b/validator/validator.c
@@ -64,10 +64,15 @@
#include "sldns/wire2str.h"
#include "sldns/str2wire.h"
+/** Max number of RRSIGs to validate at once, suspend query for later. */
+#define MAX_VALIDATE_AT_ONCE 8
+/** Max number of validation suspends allowed, error out otherwise. */
+#define MAX_VALIDATION_SUSPENDS 16
+
/* forward decl for cache response and normal super inform calls of a DS */
static void process_ds_response(struct module_qstate* qstate,
struct val_qstate* vq, int id, int rcode, struct dns_msg* msg,
- struct query_info* qinfo, struct sock_list* origin);
+ struct query_info* qinfo, struct sock_list* origin, int* suspend);
/* Updates the suplied EDE (RFC8914) code selectively so we don't lose
@@ -292,6 +297,21 @@ val_new(struct module_qstate* qstate, int id)
return val_new_getmsg(qstate, vq);
}
+/** reset validator query state for query restart */
+static void
+val_restart(struct val_qstate* vq)
+{
+ struct comm_timer* temp_timer;
+ int restart_count;
+ if(!vq) return;
+ temp_timer = vq->suspend_timer;
+ restart_count = vq->restart_count+1;
+ memset(vq, 0, sizeof(*vq));
+ vq->suspend_timer = temp_timer;
+ vq->restart_count = restart_count;
+ vq->state = VAL_INIT_STATE;
+}
+
/**
* Exit validation with an error status
*
@@ -598,30 +618,42 @@ prime_trust_anchor(struct module_qstate* qstate, struct val_qstate* vq,
* completed.
*
* @param qstate: query state.
+ * @param vq: validator query state.
* @param env: module env for verify.
* @param ve: validator env for verify.
* @param qchase: query that was made.
* @param chase_reply: answer to validate.
* @param key_entry: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
* @return false if any of the rrsets in the an or ns sections of the message
* fail to verify. The message is then set to bogus.
*/
static int
-validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
- struct val_env* ve, struct query_info* qchase,
- struct reply_info* chase_reply, struct key_entry_key* key_entry)
+validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq,
+ struct module_env* env, struct val_env* ve, struct query_info* qchase,
+ struct reply_info* chase_reply, struct key_entry_key* key_entry,
+ int* suspend)
{
uint8_t* sname;
size_t i, slen;
struct ub_packed_rrset_key* s;
enum sec_status sec;
- int dname_seen = 0;
+ int dname_seen = 0, num_verifies = 0, verified, have_state = 0;
char* reason = NULL;
sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ *suspend = 0;
+ if(vq->msg_signatures_state) {
+ /* Pick up the state, and reset it, may not be needed now. */
+ vq->msg_signatures_state = 0;
+ have_state = 1;
+ }
/* validate the ANSWER section */
for(i=0; i<chase_reply->an_numrrsets; i++) {
+ if(have_state && i <= vq->msg_signatures_index)
+ continue;
s = chase_reply->rrsets[i];
/* Skip the CNAME following a (validated) DNAME.
* Because of the normalization routines in the iterator,
@@ -640,7 +672,7 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
/* Verify the answer rrset */
sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason,
- &reason_bogus, LDNS_SECTION_ANSWER, qstate);
+ &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified);
/* If the (answer) rrset failed to validate, then this
* message is BAD. */
if(sec != sec_status_secure) {
@@ -665,14 +697,33 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) {
dname_seen = 1;
}
+ num_verifies += verified;
+ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
+ i+1 < (env->cfg->val_clean_additional?
+ chase_reply->an_numrrsets+chase_reply->ns_numrrsets:
+ chase_reply->rrset_count)) {
+ /* If the number of RRSIGs exceeds the maximum in
+ * one go, suspend. Only suspend if there is a next
+ * rrset to verify, i+1<loopmax. Store where to
+ * continue later. */
+ *suspend = 1;
+ vq->msg_signatures_state = 1;
+ vq->msg_signatures_index = i;
+ verbose(VERB_ALGO, "msg signature validation "
+ "suspended");
+ return 0;
+ }
}
/* validate the AUTHORITY section */
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
+ if(have_state && i <= vq->msg_signatures_index)
+ continue;
s = chase_reply->rrsets[i];
sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason,
- &reason_bogus, LDNS_SECTION_AUTHORITY, qstate);
+ &reason_bogus, LDNS_SECTION_AUTHORITY, qstate,
+ &verified);
/* If anything in the authority section fails to be secure,
* we have a bad message. */
if(sec != sec_status_secure) {
@@ -686,6 +737,18 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
update_reason_bogus(chase_reply, reason_bogus);
return 0;
}
+ num_verifies += verified;
+ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
+ i+1 < (env->cfg->val_clean_additional?
+ chase_reply->an_numrrsets+chase_reply->ns_numrrsets:
+ chase_reply->rrset_count)) {
+ *suspend = 1;
+ vq->msg_signatures_state = 1;
+ vq->msg_signatures_index = i;
+ verbose(VERB_ALGO, "msg signature validation "
+ "suspended");
+ return 0;
+ }
}
/* If set, the validator should clean the additional section of
@@ -695,22 +758,103 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
/* attempt to validate the ADDITIONAL section rrsets */
for(i=chase_reply->an_numrrsets+chase_reply->ns_numrrsets;
i<chase_reply->rrset_count; i++) {
+ if(have_state && i <= vq->msg_signatures_index)
+ continue;
s = chase_reply->rrsets[i];
/* only validate rrs that have signatures with the key */
/* leave others unchecked, those get removed later on too */
val_find_rrset_signer(s, &sname, &slen);
+ verified = 0;
if(sname && query_dname_compare(sname, key_entry->name)==0)
(void)val_verify_rrset_entry(env, ve, s, key_entry,
- &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate);
+ &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate,
+ &verified);
/* the additional section can fail to be secure,
* it is optional, check signature in case we need
* to clean the additional section later. */
+ num_verifies += verified;
+ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
+ i+1 < chase_reply->rrset_count) {
+ *suspend = 1;
+ vq->msg_signatures_state = 1;
+ vq->msg_signatures_index = i;
+ verbose(VERB_ALGO, "msg signature validation "
+ "suspended");
+ return 0;
+ }
}
return 1;
}
+void
+validate_suspend_timer_cb(void* arg)
+{
+ struct module_qstate* qstate = (struct module_qstate*)arg;
+ verbose(VERB_ALGO, "validate_suspend timer, continue");
+ mesh_run(qstate->env->mesh, qstate->mesh_info, module_event_pass,
+ NULL);
+}
+
+/** Setup timer to continue validation of msg signatures later */
+static int
+validate_suspend_setup_timer(struct module_qstate* qstate,
+ struct val_qstate* vq, int id, enum val_state resume_state)
+{
+ struct timeval tv;
+ int usec, slack, base;
+ if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) {
+ verbose(VERB_ALGO, "validate_suspend timer: "
+ "reached MAX_VALIDATION_SUSPENDS (%d); error out",
+ MAX_VALIDATION_SUSPENDS);
+ errinf(qstate, "max validation suspends reached, "
+ "too many RRSIG validations");
+ return 0;
+ }
+ verbose(VERB_ALGO, "validate_suspend timer, set for suspend");
+ vq->state = resume_state;
+ qstate->ext_state[id] = module_wait_reply;
+ if(!vq->suspend_timer) {
+ vq->suspend_timer = comm_timer_create(
+ qstate->env->worker_base,
+ validate_suspend_timer_cb, qstate);
+ if(!vq->suspend_timer) {
+ log_err("validate_suspend_setup_timer: "
+ "out of memory for comm_timer_create");
+ return 0;
+ }
+ }
+ /* The timer is activated later, after other events in the event
+ * loop have been processed. The query state can also be deleted,
+ * when the list is full and query states are dropped. */
+ /* Extend wait time if there are a lot of queries or if this one
+ * is taking long, to keep around cpu time for ordinary queries. */
+ usec = 50000; /* 50 msec */
+ slack = 0;
+ if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states)
+ slack += 3;
+ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/2)
+ slack += 2;
+ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/4)
+ slack += 1;
+ if(vq->suspend_count > 3)
+ slack += 3;
+ else if(vq->suspend_count > 0)
+ slack += vq->suspend_count;
+ if(slack != 0 && slack <= 12 /* No numeric overflow. */) {
+ usec = usec << slack;
+ }
+ /* Spread such timeouts within 90%-100% of the original timer. */
+ base = usec * 9/10;
+ usec = base + ub_random_max(qstate->env->rnd, usec-base);
+ tv.tv_usec = (usec % 1000000);
+ tv.tv_sec = (usec / 1000000);
+ vq->suspend_count ++;
+ comm_timer_set(vq->suspend_timer, &tv);
+ return 1;
+}
+
/**
* Detect wrong truncated response (say from BIND 9.6.1 that is forwarding
* and saw the NS record without signatures from a referral).
@@ -809,11 +953,17 @@ remove_spurious_authority(struct reply_info* chase_reply,
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_positive_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
uint8_t* wc = NULL;
size_t wl;
@@ -822,6 +972,7 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
+ *suspend = 0;
/* validate the ANSWER section - this will be the answer itself */
for(i=0; i<chase_reply->an_numrrsets; i++) {
@@ -873,17 +1024,23 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
/* If this was a positive wildcard response that we haven't already
* proven, and we have NSEC3 records, try to prove it using the NSEC3
* records. */
- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
- enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ enum sec_status sec = nsec3_prove_wildcard(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey, wc);
+ chase_reply->ns_numrrsets, qchase, kkey, wc,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "Positive wildcard response is "
"insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
wc_NSEC_ok = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ }
}
/* If after all this, we still haven't proven the positive wildcard
@@ -915,11 +1072,17 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_nodata_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
/* Since we are here, there must be nothing in the ANSWER section to
* validate. */
@@ -936,6 +1099,7 @@ validate_nodata_response(struct module_env* env, struct val_env* ve,
int nsec3s_seen = 0; /* nsec3s seen */
struct ub_packed_rrset_key* s;
size_t i;
+ *suspend = 0;
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
@@ -974,16 +1138,23 @@ validate_nodata_response(struct module_env* env, struct val_env* ve,
}
}
- if(!has_valid_nsec && nsec3s_seen) {
+ if(!has_valid_nsec && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
enum sec_status sec = nsec3_prove_nodata(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey);
+ chase_reply->ns_numrrsets, qchase, kkey,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "NODATA response is insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
has_valid_nsec = 1;
+ } else if(sec == sec_status_unchecked) {
+ /* check is incomplete; suspend */
+ *suspend = 1;
+ return;
+ }
}
if(!has_valid_nsec) {
@@ -1015,11 +1186,18 @@ validate_nodata_response(struct module_env* env, struct val_env* ve,
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
* @param rcode: adjusted RCODE, in case of RCODE/proof mismatch leniency.
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_nameerror_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey, int* rcode)
+ struct key_entry_key* kkey, int* rcode,
+ struct module_qstate* qstate, struct val_qstate* vq,
+ int* nsec3_calculations, int* suspend)
{
int has_valid_nsec = 0;
int has_valid_wnsec = 0;
@@ -1029,6 +1207,7 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve,
uint8_t* ce;
int ce_labs = 0;
int prev_ce_labs = 0;
+ *suspend = 0;
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
@@ -1058,13 +1237,18 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve,
nsec3s_seen = 1;
}
- if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen) {
+ if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
/* use NSEC3 proof, both answer and auth rrsets, in case
* NSEC3s end up in the answer (due to qtype=NSEC3 or so) */
chase_reply->security = nsec3_prove_nameerror(env, ve,
chase_reply->rrsets, chase_reply->an_numrrsets+
- chase_reply->ns_numrrsets, qchase, kkey);
- if(chase_reply->security != sec_status_secure) {
+ chase_reply->ns_numrrsets, qchase, kkey,
+ &vq->nsec3_cache_table, nsec3_calculations);
+ if(chase_reply->security == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ } else if(chase_reply->security != sec_status_secure) {
verbose(VERB_QUERY, "NameError response failed nsec, "
"nsec3 proof was %s", sec_status_to_string(
chase_reply->security));
@@ -1076,26 +1260,34 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve,
/* If the message fails to prove either condition, it is bogus. */
if(!has_valid_nsec) {
+ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
+ qstate, vq, nsec3_calculations, suspend);
+ if(*suspend) return;
verbose(VERB_QUERY, "NameError response has failed to prove: "
"qname does not exist");
- chase_reply->security = sec_status_bogus;
- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
/* Be lenient with RCODE in NSEC NameError responses */
- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
- if (chase_reply->security == sec_status_secure)
+ if(chase_reply->security == sec_status_secure) {
*rcode = LDNS_RCODE_NOERROR;
+ } else {
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ }
return;
}
if(!has_valid_wnsec) {
+ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
+ qstate, vq, nsec3_calculations, suspend);
+ if(*suspend) return;
verbose(VERB_QUERY, "NameError response has failed to prove: "
"covering wildcard does not exist");
- chase_reply->security = sec_status_bogus;
- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
/* Be lenient with RCODE in NSEC NameError responses */
- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
- if (chase_reply->security == sec_status_secure)
+ if (chase_reply->security == sec_status_secure) {
*rcode = LDNS_RCODE_NOERROR;
+ } else {
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ }
return;
}
@@ -1155,11 +1347,17 @@ validate_referral_response(struct reply_info* chase_reply)
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_any_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
/* all answer and auth rrsets already verified */
/* but check if a wildcard response is given, then check NSEC/NSEC3
@@ -1170,6 +1368,7 @@ validate_any_response(struct module_env* env, struct val_env* ve,
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
+ *suspend = 0;
if(qchase->qtype != LDNS_RR_TYPE_ANY) {
log_err("internal error: ANY validation called for non-ANY");
@@ -1224,19 +1423,25 @@ validate_any_response(struct module_env* env, struct val_env* ve,
/* If this was a positive wildcard response that we haven't already
* proven, and we have NSEC3 records, try to prove it using the NSEC3
* records. */
- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
/* look both in answer and auth section for NSEC3s */
- enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ enum sec_status sec = nsec3_prove_wildcard(env, ve,
chase_reply->rrsets,
- chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
- qchase, kkey, wc);
+ chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
+ qchase, kkey, wc, &vq->nsec3_cache_table,
+ nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "Positive ANY wildcard response is "
"insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
wc_NSEC_ok = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ }
}
/* If after all this, we still haven't proven the positive wildcard
@@ -1269,11 +1474,17 @@ validate_any_response(struct module_env* env, struct val_env* ve,
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_cname_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
uint8_t* wc = NULL;
size_t wl;
@@ -1281,6 +1492,7 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
+ *suspend = 0;
/* validate the ANSWER section - this will be the CNAME (+DNAME) */
for(i=0; i<chase_reply->an_numrrsets; i++) {
@@ -1345,17 +1557,23 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
/* If this was a positive wildcard response that we haven't already
* proven, and we have NSEC3 records, try to prove it using the NSEC3
* records. */
- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
- enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ enum sec_status sec = nsec3_prove_wildcard(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey, wc);
+ chase_reply->ns_numrrsets, qchase, kkey, wc,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "wildcard CNAME response is "
"insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
wc_NSEC_ok = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ }
}
/* If after all this, we still haven't proven the positive wildcard
@@ -1386,11 +1604,17 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/
uint8_t* ce = NULL; /* for wildcard nodata responses. This is the
@@ -1404,6 +1628,7 @@ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
uint8_t* nsec_ce; /* Used to find the NSEC with the longest ce */
int ce_labs = 0;
int prev_ce_labs = 0;
+ *suspend = 0;
/* the AUTHORITY section */
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
@@ -1469,11 +1694,13 @@ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
return;
}
- if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) {
+ if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
int nodata;
enum sec_status sec = nsec3_prove_nxornodata(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey, &nodata);
+ chase_reply->ns_numrrsets, qchase, kkey, &nodata,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "CNAMEchain to noanswer response "
"is insecure");
@@ -1483,6 +1710,9 @@ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
if(nodata)
nodata_valid_nsec = 1;
else nxdomain_valid_nsec = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
}
}
@@ -1826,13 +2056,37 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id)
* Uses negative cache for NSEC3 lookup of DS responses. */
/* only if cache not blacklisted, of course */
struct dns_msg* msg;
- if(!qstate->blacklist && !vq->chain_blacklist &&
+ int suspend;
+ if(vq->sub_ds_msg) {
+ /* We have a suspended DS reply from a sub-query;
+ * process it. */
+ verbose(VERB_ALGO, "Process suspended sub DS response");
+ msg = vq->sub_ds_msg;
+ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
+ msg, &msg->qinfo, NULL, &suspend);
+ if(suspend) {
+ /* we'll come back here later to continue */
+ if(!validate_suspend_setup_timer(qstate, vq,
+ id, VAL_FINDKEY_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
+ vq->sub_ds_msg = NULL;
+ return 1; /* continue processing ds-response results */
+ } else if(!qstate->blacklist && !vq->chain_blacklist &&
(msg=val_find_DS(qstate->env, target_key_name,
target_key_len, vq->qchase.qclass, qstate->region,
vq->key_entry->name)) ) {
verbose(VERB_ALGO, "Process cached DS response");
process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
- msg, &msg->qinfo, NULL);
+ msg, &msg->qinfo, NULL, &suspend);
+ if(suspend) {
+ /* we'll come back here later to continue */
+ if(!validate_suspend_setup_timer(qstate, vq,
+ id, VAL_FINDKEY_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
return 1; /* continue processing ds-response results */
}
if(!generate_request(qstate, id, target_key_name,
@@ -1875,7 +2129,7 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
struct val_env* ve, int id)
{
enum val_classification subtype;
- int rcode;
+ int rcode, suspend, nsec3_calculations = 0;
if(!vq->key_entry) {
verbose(VERB_ALGO, "validate: no key entry, failed");
@@ -1932,8 +2186,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
/* check signatures in the message;
* answer and authority must be valid, additional is only checked. */
- if(!validate_msg_signatures(qstate, qstate->env, ve, &vq->qchase,
- vq->chase_reply, vq->key_entry)) {
+ if(!validate_msg_signatures(qstate, vq, qstate->env, ve, &vq->qchase,
+ vq->chase_reply, vq->key_entry, &suspend)) {
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate, vq,
+ id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
/* workaround bad recursor out there that truncates (even
* with EDNS4k) to 512 by removing RRSIG from auth section
* for positive replies*/
@@ -1962,7 +2222,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
case VAL_CLASS_POSITIVE:
verbose(VERB_ALGO, "Validating a positive response");
validate_positive_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(positive): %s",
sec_status_to_string(
vq->chase_reply->security));
@@ -1971,7 +2238,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
case VAL_CLASS_NODATA:
verbose(VERB_ALGO, "Validating a nodata response");
validate_nodata_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(nodata): %s",
sec_status_to_string(
vq->chase_reply->security));
@@ -1981,7 +2255,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags);
verbose(VERB_ALGO, "Validating a nxdomain response");
validate_nameerror_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry, &rcode);
+ &vq->qchase, vq->chase_reply, vq->key_entry, &rcode,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(nxdomain): %s",
sec_status_to_string(
vq->chase_reply->security));
@@ -1992,7 +2273,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
case VAL_CLASS_CNAME:
verbose(VERB_ALGO, "Validating a cname response");
validate_cname_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(cname): %s",
sec_status_to_string(
vq->chase_reply->security));
@@ -2002,7 +2290,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
verbose(VERB_ALGO, "Validating a cname noanswer "
"response");
validate_cname_noanswer_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(cname_noanswer): %s",
sec_status_to_string(
vq->chase_reply->security));
@@ -2019,8 +2314,15 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq,
case VAL_CLASS_ANY:
verbose(VERB_ALGO, "Validating a positive ANY "
"response");
- validate_any_response(qstate->env, ve, &vq->qchase,
- vq->chase_reply, vq->key_entry);
+ validate_any_response(qstate->env, ve, &vq->qchase,
+ vq->chase_reply, vq->key_entry, qstate, vq,
+ &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(positive_any): %s",
sec_status_to_string(
vq->chase_reply->security));
@@ -2129,16 +2431,13 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
if(vq->orig_msg->rep->security == sec_status_bogus) {
/* see if we can try again to fetch data */
if(vq->restart_count < ve->max_restart) {
- int restart_count = vq->restart_count+1;
verbose(VERB_ALGO, "validation failed, "
"blacklist and retry to fetch data");
val_blacklist(&qstate->blacklist, qstate->region,
qstate->reply_origin, 0);
qstate->reply_origin = NULL;
qstate->errinf = NULL;
- memset(vq, 0, sizeof(*vq));
- vq->restart_count = restart_count;
- vq->state = VAL_INIT_STATE;
+ val_restart(vq);
verbose(VERB_ALGO, "pass back to next module");
qstate->ext_state[id] = module_restart_next;
return 0;
@@ -2465,7 +2764,10 @@ primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset,
* DS response indicated an end to secure space, is_good if the DS
* validated. It returns ke=NULL if the DS response indicated that the
* request wasn't a delegation point.
- * @return 0 on servfail error (malloc failure).
+ * @return
+ * 0 on success,
+ * 1 on servfail error (malloc failure),
+ * 2 on NSEC3 suspend.
*/
static int
ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
@@ -2476,6 +2778,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
char* reason = NULL;
sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
enum val_classification subtype;
+ int verified;
if(rcode != LDNS_RCODE_NOERROR) {
char rc[16];
rc[0]=0;
@@ -2506,7 +2809,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
/* Verify only returns BOGUS or SECURE. If the rrset is
* bogus, then we are done. */
sec = val_verify_rrset_entry(qstate->env, ve, ds,
- vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate);
+ vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified);
if(sec != sec_status_secure) {
verbose(VERB_DETAIL, "DS rrset in DS response did "
"not verify");
@@ -2524,7 +2827,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
ub_packed_rrset_ttl(ds),
LDNS_EDE_UNSUPPORTED_DS_DIGEST, NULL,
*qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
}
/* Otherwise, we return the positive response. */
@@ -2532,7 +2835,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
*ke = key_entry_create_rrset(qstate->region,
qinfo->qname, qinfo->qname_len, qinfo->qclass, ds,
NULL, LDNS_EDE_NONE, NULL, *qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
} else if(subtype == VAL_CLASS_NODATA ||
subtype == VAL_CLASS_NAMEERROR) {
/* NODATA means that the qname exists, but that there was
@@ -2566,12 +2869,12 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
qinfo->qclass, proof_ttl,
LDNS_EDE_NONE, NULL,
*qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
case sec_status_insecure:
verbose(VERB_DETAIL, "NSEC RRset for the "
"referral proved not a delegation point");
*ke = NULL;
- return 1;
+ return 0;
case sec_status_bogus:
verbose(VERB_DETAIL, "NSEC RRset for the "
"referral did not prove no DS.");
@@ -2583,10 +2886,17 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
break;
}
+ if(!nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ log_err("malloc failure in ds_response_to_ke for "
+ "NSEC3 cache");
+ reason = "malloc failure";
+ errinf_ede(qstate, reason, 0);
+ goto return_bogus;
+ }
sec = nsec3_prove_nods(qstate->env, ve,
msg->rep->rrsets + msg->rep->an_numrrsets,
msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason,
- &reason_bogus, qstate);
+ &reason_bogus, qstate, &vq->nsec3_cache_table);
switch(sec) {
case sec_status_insecure:
/* case insecure also continues to unsigned
@@ -2600,18 +2910,19 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
qinfo->qclass, proof_ttl,
LDNS_EDE_NONE, NULL,
*qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
case sec_status_indeterminate:
verbose(VERB_DETAIL, "NSEC3s for the "
"referral proved no delegation");
*ke = NULL;
- return 1;
+ return 0;
case sec_status_bogus:
verbose(VERB_DETAIL, "NSEC3s for the "
"referral did not prove no DS.");
errinf_ede(qstate, reason, reason_bogus);
goto return_bogus;
case sec_status_unchecked:
+ return 2;
default:
/* NSEC3 proof did not work */
break;
@@ -2652,13 +2963,13 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
}
sec = val_verify_rrset_entry(qstate->env, ve, cname,
vq->key_entry, &reason, &reason_bogus,
- LDNS_SECTION_ANSWER, qstate);
+ LDNS_SECTION_ANSWER, qstate, &verified);
if(sec == sec_status_secure) {
verbose(VERB_ALGO, "CNAME validated, "
"proof that DS does not exist");
/* and that it is not a referral point */
*ke = NULL;
- return 1;
+ return 0;
}
errinf(qstate, "CNAME in DS response was not secure.");
errinf_ede(qstate, reason, reason_bogus);
@@ -2682,7 +2993,7 @@ return_bogus:
*ke = key_entry_create_bad(qstate->region, qinfo->qname,
qinfo->qname_len, qinfo->qclass, BOGUS_KEY_TTL,
reason_bogus, reason, *qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
}
/**
@@ -2703,17 +3014,31 @@ return_bogus:
static void
process_ds_response(struct module_qstate* qstate, struct val_qstate* vq,
int id, int rcode, struct dns_msg* msg, struct query_info* qinfo,
- struct sock_list* origin)
+ struct sock_list* origin, int* suspend)
{
struct val_env* ve = (struct val_env*)qstate->env->modinfo[id];
struct key_entry_key* dske = NULL;
uint8_t* olds = vq->empty_DS_name;
+ int ret;
+ *suspend = 0;
vq->empty_DS_name = NULL;
- if(!ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske)) {
+ ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske);
+ if(ret != 0) {
+ switch(ret) {
+ case 1:
log_err("malloc failure in process_ds_response");
vq->key_entry = NULL; /* make it error */
vq->state = VAL_VALIDATE_STATE;
return;
+ case 2:
+ *suspend = 1;
+ return;
+ default:
+ log_err("unhandled error value for ds_response_to_ke");
+ vq->key_entry = NULL; /* make it error */
+ vq->state = VAL_VALIDATE_STATE;
+ return;
+ }
}
if(dske == NULL) {
vq->empty_DS_name = regional_alloc_init(qstate->region,
@@ -2965,9 +3290,26 @@ val_inform_super(struct module_qstate* qstate, int id,
return;
}
if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) {
+ int suspend;
process_ds_response(super, vq, id, qstate->return_rcode,
- qstate->return_msg, &qstate->qinfo,
- qstate->reply_origin);
+ qstate->return_msg, &qstate->qinfo,
+ qstate->reply_origin, &suspend);
+ /* If NSEC3 was needed during validation, NULL the NSEC3 cache;
+ * it will be re-initiated if needed later on.
+ * Validation (and the cache table) are happening/allocated in
+ * the super qstate whilst the RRs are allocated (and pointed
+ * to) in this sub qstate. */
+ if(vq->nsec3_cache_table.ct) {
+ vq->nsec3_cache_table.ct = NULL;
+ }
+ if(suspend) {
+ /* deep copy the return_msg to vq->sub_ds_msg; it will
+ * be resumed later in the super state with the caveat
+ * that the initial calculations will be re-caclulated
+ * and re-suspended there before continuing. */
+ vq->sub_ds_msg = dns_msg_deepcopy_region(
+ qstate->return_msg, super->region);
+ }
return;
} else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) {
process_dnskey_response(super, vq, id, qstate->return_rcode,
@@ -2981,8 +3323,15 @@ val_inform_super(struct module_qstate* qstate, int id,
void
val_clear(struct module_qstate* qstate, int id)
{
+ struct val_qstate* vq;
if(!qstate)
return;
+ vq = (struct val_qstate*)qstate->minfo[id];
+ if(vq) {
+ if(vq->suspend_timer) {
+ comm_timer_delete(vq->suspend_timer);
+ }
+ }
/* everything is allocated in the region, so assign NULL */
qstate->minfo[id] = NULL;
}