From: Wouter Wijngaards Date: Tue, 21 Aug 2007 13:12:10 +0000 (+0000) Subject: Returns and caches validated replies. X-Git-Tag: release-0.5~105 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=272096d6110ae4f7ca8b4e0900f6ed520b65efb9;p=thirdparty%2Funbound.git Returns and caches validated replies. git-svn-id: file:///svn/unbound/trunk@536 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/daemon/daemon.c b/daemon/daemon.c index 3ed1f78a1..944bd7da5 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -271,6 +271,7 @@ static void daemon_setup_modules(struct daemon* daemon) daemon->env->worker = NULL; daemon->env->send_packet = &worker_send_packet; daemon->env->send_query = &worker_send_query; + daemon->env->need_to_validate = 0; /* set by module init below */ for(i=0; inum_modules; i++) { log_info("init module %d: %s", i, daemon->modfunc[i]->name); if(!(*daemon->modfunc[i]->init)(daemon->env, i)) { diff --git a/daemon/worker.c b/daemon/worker.c index 209311c82..d3cd05826 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -327,6 +327,18 @@ check_cache_chain(struct reply_info* rep) { return 1; } +/** check security status in cache reply */ +static int +all_rrsets_secure(struct reply_info* rep) { + size_t i; + for(i=0; irrset_count; i++) { + if( ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security != sec_status_secure ) + return 0; + } + return 1; +} + /** answer query from the cache */ static int answer_from_cache(struct worker* worker, struct lruhash_entry* e, uint16_t id, @@ -336,6 +348,8 @@ answer_from_cache(struct worker* worker, struct lruhash_entry* e, uint16_t id, struct reply_info* rep = (struct reply_info*)e->data; uint32_t timenow = time(0); uint16_t udpsize = edns->udp_size; + int secure; + int must_validate = !(flags&BIT_CD) && worker->env.need_to_validate; /* see if it is possible */ if(rep->ttl <= timenow) { /* the rrsets may have been updated in the meantime. @@ -356,15 +370,44 @@ answer_from_cache(struct worker* worker, struct lruhash_entry* e, uint16_t id, htons(LDNS_RR_TYPE_CNAME) || rep->rrsets[0]->rk.type == htons(LDNS_RR_TYPE_DNAME))) { if(!check_cache_chain(rep)) { + /* cname chain invalid, redo iterator steps */ + verbose(VERB_ALGO, "Cache reply: cname chain broken"); + bail_out: rrset_array_unlock_touch(worker->env.rrset_cache, worker->scratchpad, rep->ref, rep->rrset_count); region_free_all(worker->scratchpad); return 0; } } + /* check security status of the cached answer */ + if( rep->security == sec_status_bogus && must_validate) { + /* BAD cached */ + error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, + &mrentry->key, id, flags, edns); + rrset_array_unlock_touch(worker->env.rrset_cache, + worker->scratchpad, rep->ref, rep->rrset_count); + region_free_all(worker->scratchpad); + return 1; + } else if( rep->security == sec_status_unchecked && must_validate) { + verbose(VERB_ALGO, "Cache reply: unchecked entry needs " + "validation"); + goto bail_out; /* need to validate cache entry first */ + } else if(rep->security == sec_status_secure) { + if(all_rrsets_secure(rep)) + secure = 1; + else { + if(must_validate) { + verbose(VERB_ALGO, "Cache reply: secure entry" + " changed status"); + goto bail_out; /* rrset changed, re-verify */ + } + secure = 0; + } + } else secure = 0; + if(!reply_info_answer_encode(&mrentry->key, rep, id, flags, repinfo->c->buffer, timenow, 1, worker->scratchpad, - udpsize, edns, (int)(edns->bits & EDNS_DO) )) { + udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, &mrentry->key, id, flags, edns); } diff --git a/doc/Changelog b/doc/Changelog index 325927ee1..7a23907b6 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,9 @@ 21 August 2007: Wouter - ANY response validation. + - store security status in cache. + - check cache security status and either send the query to be + validated, return the query to client, or send servfail to client. + Sets AD bit on validated replies. 20 August 2007: Wouter - validate and positive validation, positive wildcard NSEC validation. diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index 38cc92b96..a77bed4b1 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -296,51 +296,7 @@ int iter_dns_store(struct module_env* env, struct query_info* msgqinf, struct reply_info* msgrep, int is_referral) { - struct reply_info* rep = NULL; - /* alloc, malloc properly (not in region, like msg is) */ - rep = reply_info_copy(msgrep, env->alloc, NULL); - if(!rep) - return 0; - - if(is_referral) { - /* store rrsets */ - struct rrset_ref ref; - uint32_t now = time(NULL); - size_t i; - for(i=0; irrset_count; i++) { - packed_rrset_ttl_add((struct packed_rrset_data*) - rep->rrsets[i]->entry.data, now); - ref.key = rep->rrsets[i]; - ref.id = rep->rrsets[i]->id; - /*ignore ret: it was in the cache, ref updated */ - (void)rrset_cache_update(env->rrset_cache, &ref, - env->alloc, now); - } - free(rep); - return 1; - } else { - /* store msg, and rrsets */ - struct query_info qinf; - hashvalue_t h; - - qinf = *msgqinf; - qinf.qname = memdup(msgqinf->qname, msgqinf->qname_len); - if(!qinf.qname) { - reply_info_parsedelete(rep, env->alloc); - return 0; - } - /* fixup flags to be sensible for a reply based on the cache */ - /* this module means that RA is available. It is an answer QR. - * Not AA from cache. Not CD in cache (depends on client bit). */ - rep->flags |= (BIT_RA | BIT_QR); - rep->flags &= ~(BIT_AA | BIT_CD); - h = query_info_hash(&qinf); - dns_cache_store_msg(env, &qinf, h, rep); - /* qname is used inside query_info_entrysetup, and set to - * NULL. If it has not been used, free it. free(0) is safe. */ - free(qinf.qname); - } - return 1; + return dns_cache_store(env, msgqinf, msgrep, is_referral); } int diff --git a/services/cache/dns.c b/services/cache/dns.c index 00da8e0ee..cacc33ddd 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -399,6 +399,7 @@ tomsg(struct module_env* env, struct msgreply_entry* e, struct reply_info* r, msg->rep->flags = r->flags; msg->rep->qdcount = r->qdcount; msg->rep->ttl = r->ttl; + msg->rep->security = r->security; msg->rep->an_numrrsets = r->an_numrrsets; msg->rep->ns_numrrsets = r->ns_numrrsets; msg->rep->ar_numrrsets = r->ar_numrrsets; @@ -585,3 +586,54 @@ dns_cache_lookup(struct module_env* env, return NULL; } + +int +dns_cache_store(struct module_env* env, struct query_info* msgqinf, + struct reply_info* msgrep, int is_referral) +{ + struct reply_info* rep = NULL; + /* alloc, malloc properly (not in region, like msg is) */ + rep = reply_info_copy(msgrep, env->alloc, NULL); + if(!rep) + return 0; + + if(is_referral) { + /* store rrsets */ + struct rrset_ref ref; + uint32_t now = time(NULL); + size_t i; + for(i=0; irrset_count; i++) { + packed_rrset_ttl_add((struct packed_rrset_data*) + rep->rrsets[i]->entry.data, now); + ref.key = rep->rrsets[i]; + ref.id = rep->rrsets[i]->id; + /*ignore ret: it was in the cache, ref updated */ + (void)rrset_cache_update(env->rrset_cache, &ref, + env->alloc, now); + } + free(rep); + return 1; + } else { + /* store msg, and rrsets */ + struct query_info qinf; + hashvalue_t h; + + qinf = *msgqinf; + qinf.qname = memdup(msgqinf->qname, msgqinf->qname_len); + if(!qinf.qname) { + reply_info_parsedelete(rep, env->alloc); + return 0; + } + /* fixup flags to be sensible for a reply based on the cache */ + /* this module means that RA is available. It is an answer QR. + * Not AA from cache. Not CD in cache (depends on client bit). */ + rep->flags |= (BIT_RA | BIT_QR); + rep->flags &= ~(BIT_AA | BIT_CD); + h = query_info_hash(&qinf); + dns_cache_store_msg(env, &qinf, h, rep); + /* qname is used inside query_info_entrysetup, and set to + * NULL. If it has not been used, free it. free(0) is safe. */ + free(qinf.qname); + } + return 1; +} diff --git a/services/cache/dns.h b/services/cache/dns.h index a9849d954..d694a8663 100644 --- a/services/cache/dns.h +++ b/services/cache/dns.h @@ -59,6 +59,24 @@ struct dns_msg { struct reply_info *rep; }; +/** + * Allocate a dns_msg with malloc/alloc structure and store in dns cache. + * + * @param env: environment, with alloc structure and dns cache. + * @param qinf: query info, the query for which answer is stored. + * this is allocated in a region, and will be copied to malloc area + * before insertion. + * @param rep: reply in dns_msg from dns_alloc_msg for example. + * this is allocated in a region, and will be copied to malloc area + * before insertion. + * @param is_referral: If true, then the given message to be stored is a + * referral. The cache implementation may use this as a hint. + * It will store only the RRsets, not the message. + * @return 0 on alloc error (out of memory). + */ +int dns_cache_store(struct module_env* env, struct query_info* qinf, + struct reply_info* rep, int is_referral); + /** * Store message in the cache. Stores in message cache and rrset cache. * Both qinfo and rep should be malloced and are put in the cache. diff --git a/services/mesh.c b/services/mesh.c index 6c085207a..2adc732c1 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -425,6 +425,15 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, struct mesh_reply* r) { struct timeval end_time; + int secure; + /* examine security status */ + if(m->s.env->need_to_validate && !(r->qflags&BIT_CD) && + rep->security <= sec_status_bogus) { + rcode = LDNS_RCODE_SERVFAIL; + } + if(rep->security == sec_status_secure) + secure = 1; + else secure = 0; /* send the reply */ if(rcode) { error_encode(r->query_reply.c->buffer, rcode, &m->s.qinfo, @@ -439,7 +448,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, if(!reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->query_reply.c->buffer, 0, 1, m->s.env->scratch, udp_size, &r->edns, - (int)(r->edns.bits & EDNS_DO))) + (int)(r->edns.bits & EDNS_DO), secure)) { error_encode(r->query_reply.c->buffer, LDNS_RCODE_SERVFAIL, &m->s.qinfo, r->qid, diff --git a/util/data/msgencode.c b/util/data/msgencode.c index 6db5619ce..a17653d73 100644 --- a/util/data/msgencode.c +++ b/util/data/msgencode.c @@ -706,7 +706,7 @@ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, ldns_buffer* pkt, uint32_t timenow, int cached, struct region* region, uint16_t udpsize, - struct edns_data* edns, int dnssec) + struct edns_data* edns, int dnssec, int secure) { uint16_t flags; int attach_edns = 1; @@ -718,6 +718,8 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, /* remove AA bit, copy RD and CD bits from query. */ flags = (rep->flags & ~BIT_AA) | (qflags & (BIT_RD|BIT_CD)); } + if(secure) + flags |= BIT_AD; log_assert(flags & BIT_QR); /* QR bit must be on in our replies */ if(udpsize < LDNS_HEADER_SIZE) return 0; diff --git a/util/data/msgencode.h b/util/data/msgencode.h index 11fae6be1..3040c235c 100644 --- a/util/data/msgencode.h +++ b/util/data/msgencode.h @@ -62,12 +62,13 @@ struct edns_data; * @param edns: EDNS data included in the answer, NULL for none. * or if edns_present = 0, it is not included. * @param dnssec: if 0 DNSSEC records are omitted from the answer. + * @param secure: if 1, the AD bit is set in the reply. * @return: 0 on error (server failure). */ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, uint16_t id, uint16_t qflags, ldns_buffer* dest, uint32_t timenow, int cached, struct region* region, uint16_t udpsize, - struct edns_data* edns, int dnssec); + struct edns_data* edns, int dnssec, int secure); /** * Regenerate the wireformat from the stored msg reply. diff --git a/util/data/packed_rrset.h b/util/data/packed_rrset.h index bff8bec53..f00052779 100644 --- a/util/data/packed_rrset.h +++ b/util/data/packed_rrset.h @@ -151,6 +151,7 @@ enum rrset_trust { /** * Security status from validation for data. + * The order is significant; more secure, more proven later. */ enum sec_status { /** UNCHECKED means that object has yet to be validated. */ diff --git a/util/module.h b/util/module.h index 2d378ead1..1115d36df 100644 --- a/util/module.h +++ b/util/module.h @@ -191,6 +191,9 @@ struct module_env { struct alloc_cache* alloc; /** random table to generate random numbers */ struct ub_randstate* rnd; + /** is validation required for messages, controls client-facing + * validation status (AD bits) and servfails */ + int need_to_validate; /** module specific data. indexed by module id. */ void* modinfo[MAX_MODULE]; }; diff --git a/util/net_help.h b/util/net_help.h index 6c7eff6ef..c25844502 100644 --- a/util/net_help.h +++ b/util/net_help.h @@ -43,15 +43,25 @@ #define NET_HELP_H #include "util/log.h" -/** DNS constants for uint16_t style flag manipulation. host byteorder. */ -/** AA flag */ -#define BIT_AA 0x0400 -/** RD flag */ -#define BIT_RD 0x0100 -/** RA flag */ -#define BIT_RA 0x0080 +/** DNS constants for uint16_t style flag manipulation. host byteorder. + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ /** CD flag */ #define BIT_CD 0x0010 +/** AD flag */ +#define BIT_AD 0x0020 +/** RA flag */ +#define BIT_RA 0x0080 +/** RD flag */ +#define BIT_RD 0x0100 +/** TC flag */ +#define BIT_TC 0x0200 +/** AA flag */ +#define BIT_AA 0x0400 /** QR flag */ #define BIT_QR 0x8000 /** get RCODE bits from uint16 flags */ diff --git a/validator/val_utils.c b/validator/val_utils.c index a080d359e..9a7d6439f 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -200,12 +200,27 @@ val_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys) { enum sec_status sec; + struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> + entry.data; + if(d->security == sec_status_secure) { + /* re-verify all other statuses, because keyset may change*/ + log_nametypeclass(VERB_ALGO, "verify rrset cached", + rrset->rk.dname, ntohs(rrset->rk.type), + ntohs(rrset->rk.rrset_class)); + return d->security; + } log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); sec = dnskeyset_verify_rrset(env, ve, rrset, keys); verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); - /* TODO: update rrset security status */ + /* update rrset security status + * only improves security status */ + if(sec > d->security) { + d->security = sec; + if(sec == sec_status_secure) + d->trust = rrset_trust_validated; + } return sec; } @@ -307,8 +322,6 @@ val_verify_new_DNSKEYs(struct region* region, struct module_env* env, ds_rrset, i); if(sec == sec_status_secure) { verbose(VERB_ALGO, "DS matched DNSKEY."); - /* TODO -- cannot, wrong region for prime */ - /* update dnskey RRset status as secure */ return key_entry_create_rrset(region, ds_rrset->rk.dname, ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class), dnskey_rrset); diff --git a/validator/validator.c b/validator/validator.c index 60de3298f..c662eea46 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -89,6 +89,7 @@ val_init(struct module_env* env, int id) return 0; } env->modinfo[id] = (void*)val_env; + env->need_to_validate = 1; if(!val_apply_cfg(val_env, env->cfg)) { log_err("validator: could not apply configuration settings."); return 0; @@ -300,6 +301,12 @@ validate_positive_response(struct module_env* env, struct val_env* ve, * (unless qtype=DNAME). */ if(dname_seen && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) { dname_seen = 0; + /* CNAME was synthesized by our own iterator */ + /* since the DNAME verified, mark the CNAME as secure */ + ((struct packed_rrset_data*)s->entry.data)->security = + sec_status_secure; + ((struct packed_rrset_data*)s->entry.data)->trust = + rrset_trust_validated; continue; } @@ -665,14 +672,18 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, uint8_t* lookup_name; size_t lookup_len; if(!needs_validation(qstate, vq)) { - vq->state = vq->final_state; - return 1; + qstate->return_rcode = LDNS_RCODE_NOERROR; + qstate->return_msg = vq->orig_msg; + qstate->ext_state[id] = module_finished; + return 0; } vq->trust_anchor = anchors_lookup(ve->anchors, vq->qchase.qname, vq->qchase.qname_len, vq->qchase.qclass); if(vq->trust_anchor == NULL) { /*response isn't under a trust anchor, so we cannot validate.*/ - vq->state = vq->final_state; + vq->chase_reply->security = sec_status_indeterminate; + /* go to finished state to cache this result */ + vq->state = VAL_FINISHED_STATE; return 1; } @@ -707,7 +718,8 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, * However, we do set the status to INSECURE, since it is * essentially proven insecure. */ vq->chase_reply->security = sec_status_insecure; - vq->state = vq->final_state; + /* go to finished state to cache this result */ + vq->state = VAL_FINISHED_STATE; return 1; } @@ -911,6 +923,31 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, return 1; } +/** + * The Finished state. The validation status (good or bad) has been determined. + * + * @param qstate: query state. + * @param vq: validator query state. + * @param id: module id. + * @return true if the event should be processed further on return, false if + * not. + */ +static int +processFinished(struct module_qstate* qstate, struct val_qstate* vq, int id) +{ + /* TODO CNAME query restarts */ + + /* store results in cache */ + if(!dns_cache_store(qstate->env, &vq->qchase, vq->chase_reply, 0)) { + log_err("out of memory caching validator results"); + } + qstate->return_rcode = LDNS_RCODE_NOERROR; + qstate->return_msg = vq->orig_msg; + qstate->return_msg->rep = vq->chase_reply; + qstate->ext_state[id] = module_finished; + return 0; +} + /** * Handle validator state. * If a method returns true, the next state is started. If false, then @@ -939,6 +976,8 @@ val_handle(struct module_qstate* qstate, struct val_qstate* vq, cont = processValidate(qstate, vq, ve, id); break; case VAL_FINISHED_STATE: + cont = processFinished(qstate, vq, id); + break; default: log_warn("validator: invalid state %d", vq->state); diff --git a/validator/validator.h b/validator/validator.h index 726293a03..d19118884 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -112,9 +112,6 @@ struct val_qstate { */ struct reply_info* chase_reply; - /** This is the "final" state for the event. */ - enum val_state final_state; - /** the trust anchor rrset */ struct trust_anchor* trust_anchor;