2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 10:10:06 +00:00
bind/lib/dns/badcache.c
Ondřej Surý bc1d4c9cb4 Clear the pointer to destroyed object early using the semantic patch
Also disable the semantic patch as the code needs tweaks here and there because
some destroy functions might not destroy the object and return early if the
object is still in use.
2020-02-09 18:00:17 -08:00

419 lines
9.5 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
#include <inttypes.h>
#include <stdbool.h>
#include <isc/buffer.h>
#include <isc/log.h>
#include <isc/hash.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/platform.h>
#include <isc/print.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>
#include <dns/badcache.h>
#include <dns/name.h>
#include <dns/rdatatype.h>
#include <dns/types.h>
typedef struct dns_bcentry dns_bcentry_t;
struct dns_badcache {
unsigned int magic;
isc_mutex_t lock;
isc_mem_t *mctx;
dns_bcentry_t **table;
unsigned int count;
unsigned int minsize;
unsigned int size;
unsigned int sweep;
};
#define BADCACHE_MAGIC ISC_MAGIC('B', 'd', 'C', 'a')
#define VALID_BADCACHE(m) ISC_MAGIC_VALID(m, BADCACHE_MAGIC)
struct dns_bcentry {
dns_bcentry_t * next;
dns_rdatatype_t type;
isc_time_t expire;
uint32_t flags;
unsigned int hashval;
dns_name_t name;
};
static isc_result_t
badcache_resize(dns_badcache_t *bc, isc_time_t *now, bool grow);
isc_result_t
dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp) {
dns_badcache_t *bc = NULL;
REQUIRE(bcp != NULL && *bcp == NULL);
REQUIRE(mctx != NULL);
bc = isc_mem_get(mctx, sizeof(dns_badcache_t));
memset(bc, 0, sizeof(dns_badcache_t));
isc_mem_attach(mctx, &bc->mctx);
isc_mutex_init(&bc->lock);
bc->table = isc_mem_get(bc->mctx, sizeof(*bc->table) * size);
bc->size = bc->minsize = size;
memset(bc->table, 0, bc->size * sizeof(dns_bcentry_t *));
bc->count = 0;
bc->sweep = 0;
bc->magic = BADCACHE_MAGIC;
*bcp = bc;
return (ISC_R_SUCCESS);
}
void
dns_badcache_destroy(dns_badcache_t **bcp) {
dns_badcache_t *bc;
REQUIRE(bcp != NULL && *bcp != NULL);
bc = *bcp;
*bcp = NULL;
dns_badcache_flush(bc);
bc->magic = 0;
isc_mutex_destroy(&bc->lock);
isc_mem_put(bc->mctx, bc->table, sizeof(dns_bcentry_t *) * bc->size);
isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t));
}
static isc_result_t
badcache_resize(dns_badcache_t *bc, isc_time_t *now, bool grow) {
dns_bcentry_t **newtable, *bad, *next;
unsigned int newsize, i;
if (grow)
newsize = bc->size * 2 + 1;
else
newsize = (bc->size - 1) / 2;
newtable = isc_mem_get(bc->mctx, sizeof(dns_bcentry_t *) * newsize);
memset(newtable, 0, sizeof(dns_bcentry_t *) * newsize);
for (i = 0; bc->count > 0 && i < bc->size; i++) {
for (bad = bc->table[i]; bad != NULL; bad = next) {
next = bad->next;
if (isc_time_compare(&bad->expire, now) < 0) {
isc_mem_put(bc->mctx, bad,
sizeof(*bad) + bad->name.length);
bc->count--;
} else {
bad->next = newtable[bad->hashval % newsize];
newtable[bad->hashval % newsize] = bad;
}
}
bc->table[i] = NULL;
}
isc_mem_put(bc->mctx, bc->table, sizeof(*bc->table) * bc->size);
bc->size = newsize;
bc->table = newtable;
return (ISC_R_SUCCESS);
}
void
dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
dns_rdatatype_t type, bool update,
uint32_t flags, isc_time_t *expire)
{
isc_result_t result;
unsigned int i, hashval;
dns_bcentry_t *bad, *prev, *next;
isc_time_t now;
REQUIRE(VALID_BADCACHE(bc));
REQUIRE(name != NULL);
REQUIRE(expire != NULL);
LOCK(&bc->lock);
result = isc_time_now(&now);
if (result != ISC_R_SUCCESS)
isc_time_settoepoch(&now);
hashval = dns_name_hash(name, false);
i = hashval % bc->size;
prev = NULL;
for (bad = bc->table[i]; bad != NULL; bad = next) {
next = bad->next;
if (bad->type == type && dns_name_equal(name, &bad->name)) {
if (update) {
bad->expire = *expire;
bad->flags = flags;
}
break;
}
if (isc_time_compare(&bad->expire, &now) < 0) {
if (prev == NULL)
bc->table[i] = bad->next;
else
prev->next = bad->next;
isc_mem_put(bc->mctx, bad,
sizeof(*bad) + bad->name.length);
bc->count--;
} else
prev = bad;
}
if (bad == NULL) {
isc_buffer_t buffer;
bad = isc_mem_get(bc->mctx, sizeof(*bad) + name->length);
bad->type = type;
bad->hashval = hashval;
bad->expire = *expire;
bad->flags = flags;
isc_buffer_init(&buffer, bad + 1, name->length);
dns_name_init(&bad->name, NULL);
dns_name_copy(name, &bad->name, &buffer);
bad->next = bc->table[i];
bc->table[i] = bad;
bc->count++;
if (bc->count > bc->size * 8)
badcache_resize(bc, &now, true);
if (bc->count < bc->size * 2 && bc->size > bc->minsize)
badcache_resize(bc, &now, false);
} else
bad->expire = *expire;
UNLOCK(&bc->lock);
}
bool
dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
dns_rdatatype_t type, uint32_t *flagp,
isc_time_t *now)
{
dns_bcentry_t *bad, *prev, *next;
bool answer = false;
unsigned int i;
REQUIRE(VALID_BADCACHE(bc));
REQUIRE(name != NULL);
REQUIRE(now != NULL);
LOCK(&bc->lock);
/*
* XXXMUKS: dns_name_equal() is expensive as it does a
* octet-by-octet comparison, and it can be made better in two
* ways here. First, lowercase the names (use
* dns_name_downcase() instead of dns_name_copy() in
* dns_badcache_add()) so that dns_name_caseequal() can be used
* which the compiler will emit as SIMD instructions. Second,
* don't put multiple copies of the same name in the chain (or
* multiple names will have to be matched for equality), but use
* name->link to store the type specific part.
*/
if (bc->count == 0)
goto skip;
i = dns_name_hash(name, false) % bc->size;
prev = NULL;
for (bad = bc->table[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
bc->table[i] = bad->next;
isc_mem_put(bc->mctx, bad, sizeof(*bad) +
bad->name.length);
bc->count--;
continue;
}
if (bad->type == type && dns_name_equal(name, &bad->name)) {
if (flagp != NULL)
*flagp = bad->flags;
answer = true;
break;
}
prev = bad;
}
skip:
/*
* Slow sweep to clean out stale records.
*/
i = bc->sweep++ % bc->size;
bad = bc->table[i];
if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) {
bc->table[i] = bad->next;
isc_mem_put(bc->mctx, bad, sizeof(*bad) + bad->name.length);
bc->count--;
}
UNLOCK(&bc->lock);
return (answer);
}
void
dns_badcache_flush(dns_badcache_t *bc) {
dns_bcentry_t *entry, *next;
unsigned int i;
REQUIRE(VALID_BADCACHE(bc));
for (i = 0; bc->count > 0 && i < bc->size; i++) {
for (entry = bc->table[i]; entry != NULL; entry = next) {
next = entry->next;
isc_mem_put(bc->mctx, entry, sizeof(*entry) +
entry->name.length);
bc->count--;
}
bc->table[i] = NULL;
}
}
void
dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name) {
dns_bcentry_t *bad, *prev, *next;
isc_result_t result;
isc_time_t now;
unsigned int i;
REQUIRE(VALID_BADCACHE(bc));
REQUIRE(name != NULL);
LOCK(&bc->lock);
result = isc_time_now(&now);
if (result != ISC_R_SUCCESS)
isc_time_settoepoch(&now);
i = dns_name_hash(name, false) % bc->size;
prev = NULL;
for (bad = bc->table[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)
bc->table[i] = bad->next;
else
prev->next = bad->next;
isc_mem_put(bc->mctx, bad, sizeof(*bad) +
bad->name.length);
bc->count--;
} else
prev = bad;
}
UNLOCK(&bc->lock);
}
void
dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name) {
dns_bcentry_t *bad, *prev, *next;
unsigned int i;
int n;
isc_time_t now;
isc_result_t result;
REQUIRE(VALID_BADCACHE(bc));
REQUIRE(name != NULL);
LOCK(&bc->lock);
result = isc_time_now(&now);
if (result != ISC_R_SUCCESS)
isc_time_settoepoch(&now);
for (i = 0; bc->count > 0 && i < bc->size; i++) {
prev = NULL;
for (bad = bc->table[i]; bad != NULL; bad = next) {
next = bad->next;
n = isc_time_compare(&bad->expire, &now);
if (n < 0 || dns_name_issubdomain(&bad->name, name)) {
if (prev == NULL)
bc->table[i] = bad->next;
else
prev->next = bad->next;
isc_mem_put(bc->mctx, bad, sizeof(*bad) +
bad->name.length);
bc->count--;
} else
prev = bad;
}
}
UNLOCK(&bc->lock);
}
void
dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) {
char namebuf[DNS_NAME_FORMATSIZE];
char typebuf[DNS_RDATATYPE_FORMATSIZE];
dns_bcentry_t *bad, *next, *prev;
isc_time_t now;
unsigned int i;
uint64_t t;
REQUIRE(VALID_BADCACHE(bc));
REQUIRE(cachename != NULL);
REQUIRE(fp != NULL);
LOCK(&bc->lock);
fprintf(fp, ";\n; %s\n;\n", cachename);
TIME_NOW(&now);
for (i = 0; bc->count > 0 && i < bc->size; i++) {
prev = NULL;
for (bad = bc->table[i]; bad != NULL; bad = next) {
next = bad->next;
if (isc_time_compare(&bad->expire, &now) < 0) {
if (prev != NULL)
prev->next = bad->next;
else
bc->table[i] = bad->next;
isc_mem_put(bc->mctx, bad, sizeof(*bad) +
bad->name.length);
bc->count--;
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 "
"%" PRIu64 "]\n",
namebuf, typebuf, t);
}
}
UNLOCK(&bc->lock);
}