summaryrefslogtreecommitdiff
path: root/lib/dns/resolver.c
diff options
context:
space:
mode:
authorDoug Barton <dougb@FreeBSD.org>2010-03-29 20:15:32 +0000
committerDoug Barton <dougb@FreeBSD.org>2010-03-29 20:15:32 +0000
commitbbf1dfca0159346fbb573e7a4f4c150c5906d6ac (patch)
treed51b9c271ccfb336a7f7ff7785ad82ef4c7d80ff /lib/dns/resolver.c
parente6787144c0a7f2ccb1b75e05abbd390f0fd225cd (diff)
Notes
Diffstat (limited to 'lib/dns/resolver.c')
-rw-r--r--lib/dns/resolver.c417
1 files changed, 385 insertions, 32 deletions
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index 7b41e72da729..95c7bd4a4f84 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: resolver.c,v 1.284.18.99 2010/01/07 23:46:07 tbox Exp $ */
+/* $Id: resolver.c,v 1.284.18.101 2010/02/26 23:46:36 tbox Exp $ */
/*! \file */
@@ -318,6 +318,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;
@@ -361,6 +373,13 @@ struct dns_resolver {
unsigned int activebuckets;
isc_boolean_t priming;
unsigned int spillat; /* clients-per-query */
+
+ /* 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. */
@@ -390,7 +409,7 @@ 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);
+static void fctx_try(fetchctx_t *fctx, 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,
@@ -1088,7 +1107,7 @@ process_sendevent(resquery_t *query, isc_event_t *event) {
if (result != ISC_R_SUCCESS)
fctx_done(fctx, result, __LINE__);
else
- fctx_try(fctx);
+ fctx_try(fctx, ISC_FALSE);
}
}
@@ -1946,7 +1965,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);
+ fctx_try(fctx, ISC_FALSE);
}
}
@@ -2008,7 +2027,7 @@ fctx_finddone(isc_task_t *task, isc_event_t *event) {
dns_adb_destroyfind(&find);
if (want_try)
- fctx_try(fctx);
+ fctx_try(fctx, ISC_FALSE);
else if (want_done)
fctx_done(fctx, ISC_R_FAILURE, __LINE__);
else if (bucket_empty)
@@ -2371,7 +2390,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;
@@ -2590,12 +2609,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 {
@@ -2817,7 +2848,7 @@ fctx_nextaddress(fetchctx_t *fctx) {
}
static void
-fctx_try(fetchctx_t *fctx) {
+fctx_try(fetchctx_t *fctx, isc_boolean_t badcache) {
isc_result_t result;
dns_adbaddrinfo_t *addrinfo;
@@ -2835,7 +2866,7 @@ fctx_try(fetchctx_t *fctx) {
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.
@@ -2995,7 +3026,7 @@ fctx_timeout(isc_task_t *task, isc_event_t *event) {
/*
* Keep trying.
*/
- fctx_try(fctx);
+ fctx_try(fctx, ISC_FALSE);
}
isc_event_free(&event);
@@ -3165,7 +3196,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);
+ fctx_try(fctx, ISC_FALSE);
} else if (bucket_empty)
empty_bucket(res);
}
@@ -3740,6 +3771,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.
@@ -3785,36 +3818,80 @@ validated(isc_task_t *task, isc_event_t *event) {
FCTXTRACE("validation failed");
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); /* 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); /* Locks bucket. */
return;
}
- isc_stdtime_get(&now);
-
if (negative) {
dns_rdatatype_t covers;
FCTXTRACE("nonexistence validation OK");
@@ -5589,7 +5666,7 @@ resume_dslookup(isc_task_t *task, isc_event_t *event) {
/*
* Try again.
*/
- fctx_try(fctx);
+ fctx_try(fctx, ISC_FALSE);
} else {
unsigned int n;
dns_rdataset_t *nsrdataset = NULL;
@@ -6329,7 +6406,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
/*
* Try again.
*/
- fctx_try(fctx);
+ fctx_try(fctx, ISC_FALSE);
} else if (resend) {
/*
* Resend (probably with changed options).
@@ -6390,6 +6467,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) {
@@ -6427,6 +6525,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);
@@ -6550,6 +6649,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;
@@ -7380,6 +7483,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;