diff options
author | dougb <dougb@FreeBSD.org> | 2010-03-18 19:00:35 +0000 |
---|---|---|
committer | dougb <dougb@FreeBSD.org> | 2010-03-18 19:00:35 +0000 |
commit | d645bf55b32e90960b5ca1f5177df2639a11c530 (patch) | |
tree | 273da7c37cab7a5631d7928c11fe6612bf1668a6 /contrib/bind9/lib/dns/resolver.c | |
parent | 69fc195c592c75cc18659332e25f9f5cf3d2afa8 (diff) | |
download | FreeBSD-src-d645bf55b32e90960b5ca1f5177df2639a11c530.zip FreeBSD-src-d645bf55b32e90960b5ca1f5177df2639a11c530.tar.gz |
Update to 9.6.2-P1, the latest patchfix release which deals with
the problems related to the handling of broken DNSSEC trust chains.
This fix is only relevant for those who have DNSSEC validation
enabled and configure trust anchors from third parties, either
manually, or through a system like DLV.
Diffstat (limited to 'contrib/bind9/lib/dns/resolver.c')
-rw-r--r-- | contrib/bind9/lib/dns/resolver.c | 417 |
1 files changed, 386 insertions, 31 deletions
diff --git a/contrib/bind9/lib/dns/resolver.c b/contrib/bind9/lib/dns/resolver.c index 02e96cd..244718f 100644 --- a/contrib/bind9/lib/dns/resolver.c +++ b/contrib/bind9/lib/dns/resolver.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: resolver.c,v 1.384.14.20 2010/01/07 23:47:36 tbox Exp $ */ +/* $Id: resolver.c,v 1.384.14.20.8.2 2010/02/25 10:57:12 tbox Exp $ */ /*! \file */ @@ -334,6 +334,18 @@ typedef struct alternate { ISC_LINK(struct alternate) link; } alternate_t; +typedef struct dns_badcache dns_badcache_t; +struct dns_badcache { + dns_badcache_t * next; + dns_rdatatype_t type; + isc_time_t expire; + unsigned int hashval; + dns_name_t name; +}; +#define DNS_BADCACHE_SIZE 1021 +#define DNS_BADCACHE_TTL(fctx) \ + (((fctx)->res->lame_ttl > 30 ) ? (fctx)->res->lame_ttl : 30) + struct dns_resolver { /* Unlocked. */ unsigned int magic; @@ -380,6 +392,13 @@ struct dns_resolver { isc_boolean_t priming; unsigned int spillat; /* clients-per-query */ unsigned int nextdisp; + + /* Bad cache. */ + dns_badcache_t ** badcache; + unsigned int badcount; + unsigned int badhash; + unsigned int badsweep; + /* Locked by primelock. */ dns_fetch_t * primefetch; /* Locked by nlock. */ @@ -410,7 +429,8 @@ static void empty_bucket(dns_resolver_t *res); static isc_result_t resquery_send(resquery_t *query); static void resquery_response(isc_task_t *task, isc_event_t *event); static void resquery_connected(isc_task_t *task, isc_event_t *event); -static void fctx_try(fetchctx_t *fctx, isc_boolean_t retrying); +static void fctx_try(fetchctx_t *fctx, isc_boolean_t retrying, + isc_boolean_t badcache); static isc_boolean_t fctx_destroy(fetchctx_t *fctx); static isc_result_t ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, @@ -1169,7 +1189,7 @@ process_sendevent(resquery_t *query, isc_event_t *event) { if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else - fctx_try(fctx, ISC_TRUE); + fctx_try(fctx, ISC_TRUE, ISC_FALSE); } } @@ -2071,7 +2091,7 @@ resquery_connected(isc_task_t *task, isc_event_t *event) { if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else - fctx_try(fctx, ISC_TRUE); + fctx_try(fctx, ISC_TRUE, ISC_FALSE); } } @@ -2133,7 +2153,7 @@ fctx_finddone(isc_task_t *task, isc_event_t *event) { dns_adb_destroyfind(&find); if (want_try) - fctx_try(fctx, ISC_TRUE); + fctx_try(fctx, ISC_TRUE, ISC_FALSE); else if (want_done) fctx_done(fctx, ISC_R_FAILURE, __LINE__); else if (bucket_empty) @@ -2545,7 +2565,7 @@ isstrictsubdomain(dns_name_t *name1, dns_name_t *name2) { } static isc_result_t -fctx_getaddresses(fetchctx_t *fctx) { +fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) { dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t result; dns_resolver_t *res; @@ -2764,12 +2784,24 @@ fctx_getaddresses(fetchctx_t *fctx) { */ result = DNS_R_WAIT; } else { + isc_time_t expire; + isc_interval_t i; /* * We've lost completely. We don't know any * addresses, and the ADB has told us it can't get * them. */ FCTXTRACE("no addresses"); + isc_interval_set(&i, DNS_BADCACHE_TTL(fctx), 0); + result = isc_time_nowplusinterval(&expire, &i); + if (badcache && + (fctx->type == dns_rdatatype_dnskey || + fctx->type == dns_rdatatype_dlv || + fctx->type == dns_rdatatype_ds) && + result == ISC_R_SUCCESS) + dns_resolver_addbadcache(fctx->res, + &fctx->name, + fctx->type, &expire); result = ISC_R_FAILURE; } } else { @@ -2992,7 +3024,7 @@ fctx_nextaddress(fetchctx_t *fctx) { } static void -fctx_try(fetchctx_t *fctx, isc_boolean_t retrying) { +fctx_try(fetchctx_t *fctx, isc_boolean_t retrying, isc_boolean_t badcache) { isc_result_t result; dns_adbaddrinfo_t *addrinfo; @@ -3010,7 +3042,7 @@ fctx_try(fetchctx_t *fctx, isc_boolean_t retrying) { fctx_cleanupaltfinds(fctx); fctx_cleanupforwaddrs(fctx); fctx_cleanupaltaddrs(fctx); - result = fctx_getaddresses(fctx); + result = fctx_getaddresses(fctx, badcache); if (result == DNS_R_WAIT) { /* * Sleep waiting for addresses. @@ -3175,7 +3207,7 @@ fctx_timeout(isc_task_t *task, isc_event_t *event) { /* * Keep trying. */ - fctx_try(fctx, ISC_TRUE); + fctx_try(fctx, ISC_TRUE, ISC_FALSE); } isc_event_free(&event); @@ -3345,7 +3377,7 @@ fctx_start(isc_task_t *task, isc_event_t *event) { if (result != ISC_R_SUCCESS) fctx_done(fctx, result, __LINE__); else - fctx_try(fctx, ISC_FALSE); + fctx_try(fctx, ISC_FALSE, ISC_FALSE); } else if (bucket_empty) empty_bucket(res); } @@ -3923,6 +3955,8 @@ validated(isc_task_t *task, isc_event_t *event) { LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + isc_stdtime_get(&now); + /* * If chaining, we need to make sure that the right result code is * returned, and that the rdatasets are bound. @@ -3969,35 +4003,80 @@ validated(isc_task_t *task, isc_event_t *event) { inc_stats(fctx->res, dns_resstatscounter_valfail); fctx->valfail++; fctx->vresult = vevent->result; - result = ISC_R_NOTFOUND; - if (vevent->rdataset != NULL) - result = dns_db_findnode(fctx->cache, vevent->name, - ISC_TRUE, &node); - if (result == ISC_R_SUCCESS) - (void)dns_db_deleterdataset(fctx->cache, node, NULL, - vevent->type, 0); - if (result == ISC_R_SUCCESS && vevent->sigrdataset != NULL) - (void)dns_db_deleterdataset(fctx->cache, node, NULL, - dns_rdatatype_rrsig, - vevent->type); - if (result == ISC_R_SUCCESS) - dns_db_detachnode(fctx->cache, &node); - result = vevent->result; + if (fctx->vresult != DNS_R_BROKENCHAIN) { + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) + result = dns_db_findnode(fctx->cache, + vevent->name, + ISC_TRUE, &node); + if (result == ISC_R_SUCCESS) + (void)dns_db_deleterdataset(fctx->cache, node, + NULL, + vevent->type, 0); + if (result == ISC_R_SUCCESS && + vevent->sigrdataset != NULL) + (void)dns_db_deleterdataset(fctx->cache, node, + NULL, + dns_rdatatype_rrsig, + vevent->type); + if (result == ISC_R_SUCCESS) + dns_db_detachnode(fctx->cache, &node); + } + if (fctx->vresult == DNS_R_BROKENCHAIN && !negative) { + /* + * Cache the data as pending for later validation. + */ + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) + result = dns_db_findnode(fctx->cache, + vevent->name, + ISC_TRUE, &node); + if (result == ISC_R_SUCCESS) { + (void)dns_db_addrdataset(fctx->cache, node, + NULL, now, + vevent->rdataset, 0, + NULL); + } + if (result == ISC_R_SUCCESS && + vevent->sigrdataset != NULL) + (void)dns_db_addrdataset(fctx->cache, node, + NULL, now, + vevent->sigrdataset, + 0, NULL); + if (result == ISC_R_SUCCESS) + dns_db_detachnode(fctx->cache, &node); + } + result = fctx->vresult; add_bad(fctx, addrinfo, result, badns_validation); isc_event_free(&event); UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); INSIST(fctx->validator == NULL); fctx->validator = ISC_LIST_HEAD(fctx->validators); - if (fctx->validator != NULL) { + if (fctx->validator != NULL) dns_validator_send(fctx->validator); - } else if (sentresponse) + else if (sentresponse) fctx_done(fctx, result, __LINE__); /* Locks bucket. */ - else - fctx_try(fctx, ISC_TRUE); /* Locks bucket. */ + else if (result == DNS_R_BROKENCHAIN) { + isc_result_t tresult; + isc_time_t expire; + isc_interval_t i; + + isc_interval_set(&i, DNS_BADCACHE_TTL(fctx), 0); + tresult = isc_time_nowplusinterval(&expire, &i); + if (negative && + (fctx->type == dns_rdatatype_dnskey || + fctx->type == dns_rdatatype_dlv || + fctx->type == dns_rdatatype_ds) && + tresult == ISC_R_SUCCESS) + dns_resolver_addbadcache(fctx->res, + &fctx->name, + fctx->type, &expire); + fctx_done(fctx, result, __LINE__); /* Locks bucket. */ + } else + fctx_try(fctx, ISC_TRUE, ISC_TRUE); /* Locks bucket. */ return; } - isc_stdtime_get(&now); if (negative) { dns_rdatatype_t covers; @@ -5790,7 +5869,7 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) { /* * Try again. */ - fctx_try(fctx, ISC_TRUE); + fctx_try(fctx, ISC_TRUE, ISC_FALSE); } else { unsigned int n; dns_rdataset_t *nsrdataset = NULL; @@ -6629,7 +6708,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) { /* * Try again. */ - fctx_try(fctx, !get_nameservers); + fctx_try(fctx, !get_nameservers, ISC_FALSE); } else if (resend) { /* * Resend (probably with changed options). @@ -6691,6 +6770,27 @@ resquery_response(isc_task_t *task, isc_event_t *event) { /*** *** Resolver Methods ***/ +static void +destroy_badcache(dns_resolver_t *res) { + dns_badcache_t *bad, *next; + unsigned int i; + + if (res->badcache != NULL) { + for (i = 0; i < res->badhash; i++) + for (bad = res->badcache[i]; bad != NULL; + bad = next) { + next = bad->next; + isc_mem_put(res->mctx, bad, sizeof(*bad) + + bad->name.length); + res->badcount--; + } + isc_mem_put(res->mctx, res->badcache, + sizeof(*res->badcache) * res->badhash); + res->badcache = NULL; + res->badhash = 0; + INSIST(res->badcount == 0); + } +} static void destroy(dns_resolver_t *res) { @@ -6728,6 +6828,7 @@ destroy(dns_resolver_t *res) { isc_mem_put(res->mctx, a, sizeof(*a)); } dns_resolver_reset_algorithms(res); + destroy_badcache(res); dns_resolver_resetmustbesecure(res); #if USE_ALGLOCK isc_rwlock_destroy(&res->alglock); @@ -6851,6 +6952,10 @@ dns_resolver_create(dns_view_t *view, ISC_LIST_INIT(res->alternates); res->udpsize = RECV_BUFFER_SIZE; res->algorithms = NULL; + res->badcache = NULL; + res->badcount = 0; + res->badhash = 0; + res->badsweep = 0; res->mustbesecure = NULL; res->spillatmin = res->spillat = 10; res->spillatmax = 100; @@ -7692,6 +7797,256 @@ dns_resolver_getudpsize(dns_resolver_t *resolver) { return (resolver->udpsize); } +void +dns_resolver_flushbadcache(dns_resolver_t *resolver, dns_name_t *name) { + unsigned int i; + dns_badcache_t *bad, *prev, *next; + + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + if (resolver->badcache == NULL) + goto unlock; + + if (name != NULL) { + isc_time_t now; + isc_result_t result; + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) + isc_time_settoepoch(&now); + i = dns_name_hash(name, ISC_FALSE) % resolver->badhash; + prev = NULL; + for (bad = resolver->badcache[i]; bad != NULL; bad = next) { + int n; + next = bad->next; + n = isc_time_compare(&bad->expire, &now); + if (n < 0 || dns_name_equal(name, &bad->name)) { + if (prev == NULL) + resolver->badcache[i] = bad->next; + else + prev->next = bad->next; + isc_mem_put(resolver->mctx, bad, sizeof(*bad) + + bad->name.length); + resolver->badcount--; + } else + prev = bad; + } + } else + destroy_badcache(resolver); + + unlock: + UNLOCK(&resolver->lock); + +} + +static void +resizehash(dns_resolver_t *resolver, isc_time_t *now, isc_boolean_t grow) { + unsigned int newsize; + dns_badcache_t **new, *bad, *next; + unsigned int i; + + if (grow) + newsize = resolver->badhash * 2 + 1; + else + newsize = (resolver->badhash - 1) / 2; + + new = isc_mem_get(resolver->mctx, + sizeof(*resolver->badcache) * newsize); + if (new == NULL) + return; + memset(new, 0, sizeof(*resolver->badcache) * newsize); + for (i = 0; i < resolver->badhash; i++) { + for (bad = resolver->badcache[i]; bad != NULL; bad = next) { + next = bad->next; + if (isc_time_compare(&bad->expire, now) < 0) { + isc_mem_put(resolver->mctx, bad, sizeof(*bad) + + bad->name.length); + resolver->badcount--; + } else { + bad->next = new[bad->hashval % newsize]; + new[bad->hashval % newsize] = bad; + } + } + } + isc_mem_put(resolver->mctx, resolver->badcache, + sizeof(*resolver->badcache) * resolver->badhash); + resolver->badhash = newsize; + resolver->badcache = new; +} + +void +dns_resolver_addbadcache(dns_resolver_t *resolver, dns_name_t *name, + dns_rdatatype_t type, isc_time_t *expire) +{ + isc_time_t now; + isc_result_t result = ISC_R_SUCCESS; + unsigned int i, hashval; + dns_badcache_t *bad, *prev, *next; + + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + if (resolver->badcache == NULL) { + resolver->badcache = isc_mem_get(resolver->mctx, + sizeof(*resolver->badcache) * + DNS_BADCACHE_SIZE); + if (resolver->badcache == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + resolver->badhash = DNS_BADCACHE_SIZE; + memset(resolver->badcache, 0, sizeof(*resolver->badcache) * + resolver->badhash); + } + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) + isc_time_settoepoch(&now); + hashval = dns_name_hash(name, ISC_FALSE); + i = hashval % resolver->badhash; + prev = NULL; + for (bad = resolver->badcache[i]; bad != NULL; bad = next) { + next = bad->next; + if (bad->type == type && dns_name_equal(name, &bad->name)) + break; + if (isc_time_compare(&bad->expire, &now) < 0) { + if (prev == NULL) + resolver->badcache[i] = bad->next; + else + prev->next = bad->next; + isc_mem_put(resolver->mctx, bad, sizeof(*bad) + + bad->name.length); + resolver->badcount--; + } else + prev = bad; + } + if (bad == NULL) { + isc_buffer_t buffer; + bad = isc_mem_get(resolver->mctx, sizeof(*bad) + name->length); + if (bad == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + bad->type = type; + bad->hashval = hashval; + isc_buffer_init(&buffer, bad + 1, name->length); + dns_name_init(&bad->name, NULL); + dns_name_copy(name, &bad->name, &buffer); + bad->next = resolver->badcache[i]; + resolver->badcache[i] = bad; + resolver->badcount++; + if (resolver->badcount > resolver->badhash * 8) + resizehash(resolver, &now, ISC_TRUE); + if (resolver->badcount < resolver->badhash * 2 && + resolver->badhash > DNS_BADCACHE_SIZE) + resizehash(resolver, &now, ISC_FALSE); + } + bad->expire = *expire; + cleanup: + UNLOCK(&resolver->lock); +} + +isc_boolean_t +dns_resolver_getbadcache(dns_resolver_t *resolver, dns_name_t *name, + dns_rdatatype_t type, isc_time_t *now) +{ + dns_badcache_t *bad, *prev, *next; + isc_boolean_t answer = ISC_FALSE; + unsigned int i; + + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + if (resolver->badcache == NULL) + goto unlock; + + i = dns_name_hash(name, ISC_FALSE) % resolver->badhash; + prev = NULL; + for (bad = resolver->badcache[i]; bad != NULL; bad = next) { + next = bad->next; + /* + * Search the hash list. Clean out expired records as we go. + */ + if (isc_time_compare(&bad->expire, now) < 0) { + if (prev != NULL) + prev->next = bad->next; + else + resolver->badcache[i] = bad->next; + isc_mem_put(resolver->mctx, bad, sizeof(*bad) + + bad->name.length); + resolver->badcount--; + continue; + } + if (bad->type == type && dns_name_equal(name, &bad->name)) { + answer = ISC_TRUE; + break; + } + prev = bad; + } + + /* + * Slow sweep to clean out stale records. + */ + i = resolver->badsweep++ % resolver->badhash; + bad = resolver->badcache[i]; + if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) { + resolver->badcache[i] = bad->next; + isc_mem_put(resolver->mctx, bad, sizeof(*bad) + + bad->name.length); + resolver->badcount--; + } + + unlock: + UNLOCK(&resolver->lock); + return (answer); +} + +void +dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + dns_badcache_t *bad, *next, *prev; + isc_time_t now; + unsigned int i; + isc_uint64_t t; + + LOCK(&resolver->lock); + fprintf(fp, ";\n; Bad cache\n;\n"); + + if (resolver->badcache == NULL) + goto unlock; + + TIME_NOW(&now); + for (i = 0; i < resolver->badhash; i++) { + prev = NULL; + for (bad = resolver->badcache[i]; bad != NULL; bad = next) { + next = bad->next; + if (isc_time_compare(&bad->expire, &now) < 0) { + if (prev != NULL) + prev->next = bad->next; + else + resolver->badcache[i] = bad->next; + isc_mem_put(resolver->mctx, bad, sizeof(*bad) + + bad->name.length); + resolver->badcount--; + continue; + } + prev = bad; + dns_name_format(&bad->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(bad->type, typebuf, + sizeof(typebuf)); + t = isc_time_microdiff(&bad->expire, &now); + t /= 1000; + fprintf(fp, "; %s/%s [ttl " + "%" ISC_PLATFORM_QUADFORMAT "u]\n", + namebuf, typebuf, t); + } + } + + unlock: + UNLOCK(&resolver->lock); +} + static void free_algorithm(void *node, void *arg) { unsigned char *algorithms = node; |