2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 22:15:20 +00:00

[master] DDoS mitigation features

3938.	[func]		Added quotas to be used in recursive resolvers
			that are under high query load for names in zones
			whose authoritative servers are nonresponsive or
			are experiencing a denial of service attack.

			- "fetches-per-server" limits the number of
			  simultaneous queries that can be sent to any
			  single authoritative server.  The configured
			  value is a starting point; it is automatically
			  adjusted downward if the server is partially or
			  completely non-responsive. The algorithm used to
			  adjust the quota can be configured via the
			  "fetch-quota-params" option.
			- "fetches-per-zone" limits the number of
			  simultaneous queries that can be sent for names
			  within a single domain.  (Note: Unlike
			  "fetches-per-server", this value is not
			  self-tuning.)
			- New stats counters have been added to count
			  queries spilled due to these quotas.

			See the ARM for details of these options. [RT #37125]
This commit is contained in:
Evan Hunt
2015-07-08 22:53:39 -07:00
parent e8f98ec8d4
commit 1479200aa0
41 changed files with 1976 additions and 102 deletions

View File

@@ -163,6 +163,12 @@ struct dns_adb {
isc_boolean_t growentries_sent;
isc_event_t grownames;
isc_boolean_t grownames_sent;
isc_uint32_t quota;
isc_uint32_t atr_freq;
double atr_low;
double atr_high;
double atr_discount;
};
/*
@@ -244,10 +250,18 @@ struct dns_adbentry {
unsigned int flags;
unsigned int srtt;
isc_uint16_t udpsize;
unsigned int completed;
unsigned int timeouts;
unsigned char plain;
unsigned char plainto;
unsigned char edns;
unsigned char to4096; /* Our max. */
isc_uint8_t mode;
isc_uint32_t quota;
isc_uint32_t active;
double atr;
/*
* Allow for encapsulated IPv4/IPv6 UDP packet over ethernet.
* Ethernet 1500 - IP(20) - IP6(40) - UDP(8) = 1432.
@@ -300,6 +314,7 @@ static inline dns_adbentry_t *find_entry_and_lock(dns_adb_t *,
static void dump_adb(dns_adb_t *, FILE *, isc_boolean_t debug, isc_stdtime_t);
static void print_dns_name(FILE *, dns_name_t *);
static void print_namehook_list(FILE *, const char *legend,
dns_adb_t *adb,
dns_adbnamehooklist_t *list,
isc_boolean_t debug,
isc_stdtime_t now);
@@ -335,10 +350,13 @@ static inline void link_entry(dns_adb_t *, int, dns_adbentry_t *);
static inline isc_boolean_t unlink_entry(dns_adb_t *, dns_adbentry_t *);
static isc_boolean_t kill_name(dns_adbname_t **, isc_eventtype_t);
static void water(void *, int);
static void dump_entry(FILE *, dns_adbentry_t *, isc_boolean_t, isc_stdtime_t);
static void dump_entry(FILE *, dns_adb_t *, dns_adbentry_t *,
isc_boolean_t, isc_stdtime_t);
static void adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt,
unsigned int factor, isc_stdtime_t now);
static void shutdown_task(isc_task_t *task, isc_event_t *ev);
static void log_quota(dns_adbentry_t *entry, const char *fmt, ...)
ISC_FORMAT_PRINTF(2, 3);
/*
* MUST NOT overlap DNS_ADBFIND_* flags!
@@ -1811,6 +1829,8 @@ new_adbentry(dns_adb_t *adb) {
e->flags = 0;
e->udpsize = 0;
e->edns = 0;
e->completed = 0;
e->timeouts = 0;
e->plain = 0;
e->plainto = 0;
e->to4096 = 0;
@@ -1823,6 +1843,10 @@ new_adbentry(dns_adb_t *adb) {
e->srtt = (r & 0x1f) + 1;
e->lastage = 0;
e->expires = 0;
e->active = 0;
e->mode = 0;
e->quota = adb->quota;
e->atr = 0.0;
ISC_LIST_INIT(e->lameinfo);
ISC_LINK_INIT(e, plink);
LOCK(&adb->entriescntlock);
@@ -2138,6 +2162,25 @@ entry_is_lame(dns_adb_t *adb, dns_adbentry_t *entry, dns_name_t *qname,
return (is_bad);
}
static void
log_quota(dns_adbentry_t *entry, const char *fmt, ...) {
va_list ap;
char msgbuf[2048];
char addrbuf[ISC_NETADDR_FORMATSIZE];
isc_netaddr_t netaddr;
va_start(ap, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
va_end(ap);
isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr);
isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
ISC_LOG_INFO, "adb: quota %s (%d/%d): %s",
addrbuf, entry->active, entry->quota, msgbuf);
}
static void
copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_name_t *qname,
dns_rdatatype_t qtype, dns_adbname_t *name,
@@ -2158,6 +2201,15 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_name_t *qname,
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
LOCK(&adb->entrylocks[bucket]);
if (entry->quota != 0 &&
entry->active >= entry->quota)
{
find->options |=
(DNS_ADBFIND_LAMEPRUNED|
DNS_ADBFIND_OVERQUOTA);
goto nextv4;
}
if (!FIND_RETURNLAME(find)
&& entry_is_lame(adb, entry, qname, qtype, now)) {
find->options |= DNS_ADBFIND_LAMEPRUNED;
@@ -2189,6 +2241,15 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_name_t *qname,
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
LOCK(&adb->entrylocks[bucket]);
if (entry->quota != 0 &&
entry->active >= entry->quota)
{
find->options |=
(DNS_ADBFIND_LAMEPRUNED|
DNS_ADBFIND_OVERQUOTA);
goto nextv6;
}
if (!FIND_RETURNLAME(find)
&& entry_is_lame(adb, entry, qname, qtype, now)) {
find->options |= DNS_ADBFIND_LAMEPRUNED;
@@ -2521,6 +2582,12 @@ dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr,
adb, NULL, NULL);
adb->growentries_sent = ISC_FALSE;
adb->quota = 0;
adb->atr_freq = 0;
adb->atr_low = 0.0;
adb->atr_high = 0.0;
adb->atr_discount = 0.0;
adb->nnames = nbuckets[0];
adb->namescnt = 0;
adb->names = NULL;
@@ -3431,8 +3498,10 @@ dump_adb(dns_adb_t *adb, FILE *f, isc_boolean_t debug, isc_stdtime_t now) {
fprintf(f, "\n");
print_namehook_list(f, "v4", &name->v4, debug, now);
print_namehook_list(f, "v6", &name->v6, debug, now);
print_namehook_list(f, "v4", adb,
&name->v4, debug, now);
print_namehook_list(f, "v6", adb,
&name->v6, debug, now);
if (debug)
print_fetch_list(f, name);
@@ -3447,7 +3516,7 @@ dump_adb(dns_adb_t *adb, FILE *f, isc_boolean_t debug, isc_stdtime_t now) {
entry = ISC_LIST_HEAD(adb->entries[i]);
while (entry != NULL) {
if (entry->nh == 0)
dump_entry(f, entry, debug, now);
dump_entry(f, adb, entry, debug, now);
entry = ISC_LIST_NEXT(entry, plink);
}
}
@@ -3462,8 +3531,8 @@ dump_adb(dns_adb_t *adb, FILE *f, isc_boolean_t debug, isc_stdtime_t now) {
}
static void
dump_entry(FILE *f, dns_adbentry_t *entry, isc_boolean_t debug,
isc_stdtime_t now)
dump_entry(FILE *f, dns_adb_t *adb, dns_adbentry_t *entry,
isc_boolean_t debug, isc_stdtime_t now)
{
char addrbuf[ISC_NETADDR_FORMATSIZE];
char typebuf[DNS_RDATATYPE_FORMATSIZE];
@@ -3491,10 +3560,17 @@ dump_entry(FILE *f, dns_adbentry_t *entry, isc_boolean_t debug,
}
if (entry->expires != 0)
fprintf(f, " [ttl %d]", entry->expires - now);
if (adb != NULL && adb->quota != 0 && adb->atr_freq != 0) {
fprintf(f, " [atr %0.2f] [quota %d]",
entry->atr, entry->quota);
}
fprintf(f, "\n");
for (li = ISC_LIST_HEAD(entry->lameinfo);
li != NULL;
li = ISC_LIST_NEXT(li, plink)) {
li = ISC_LIST_NEXT(li, plink))
{
fprintf(f, ";\t\t");
print_dns_name(f, &li->qname);
dns_rdatatype_format(li->qtype, typebuf, sizeof(typebuf));
@@ -3566,7 +3642,8 @@ print_dns_name(FILE *f, dns_name_t *name) {
}
static void
print_namehook_list(FILE *f, const char *legend, dns_adbnamehooklist_t *list,
print_namehook_list(FILE *f, const char *legend,
dns_adb_t *adb, dns_adbnamehooklist_t *list,
isc_boolean_t debug, isc_stdtime_t now)
{
dns_adbnamehook_t *nh;
@@ -3577,7 +3654,7 @@ print_namehook_list(FILE *f, const char *legend, dns_adbnamehooklist_t *list,
{
if (debug)
fprintf(f, ";\tHook(%s) %p\n", legend, nh);
dump_entry(f, nh->entry, debug, now);
dump_entry(f, adb, nh->entry, debug, now);
}
}
@@ -4142,6 +4219,75 @@ dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
UNLOCK(&adb->entrylocks[bucket]);
}
/*
* (10000 / ((10 + n) / 10)^(3/2)) for n in 0..99.
* These will be used to make quota adjustments.
*/
static int quota_adj[] = {
10000, 8668, 7607, 6747, 6037, 5443, 4941, 4512, 4141,
3818, 3536, 3286, 3065, 2867, 2690, 2530, 2385, 2254,
2134, 2025, 1925, 1832, 1747, 1668, 1595, 1527, 1464,
1405, 1350, 1298, 1250, 1205, 1162, 1121, 1083, 1048,
1014, 981, 922, 894, 868, 843, 820, 797, 775, 755,
735, 716, 698, 680, 664, 648, 632, 618, 603, 590, 577,
564, 552, 540, 529, 518, 507, 497, 487, 477, 468, 459,
450, 442, 434, 426, 418, 411, 404, 397, 390, 383, 377,
370, 364, 358, 353, 347, 342, 336, 331, 326, 321, 316,
312, 307, 303, 298, 294, 290, 286, 282, 278
};
/*
* Caller must hold adbentry lock
*/
static void
maybe_adjust_quota(dns_adb_t *adb, dns_adbaddrinfo_t *addr,
isc_boolean_t timeout)
{
double tr;
UNUSED(adb);
if (adb->quota == 0 || adb->atr_freq == 0)
return;
if (timeout)
addr->entry->timeouts++;
if (addr->entry->completed++ <= adb->atr_freq)
return;
/*
* Calculate an exponential rolling average of the timeout ratio
*
* XXX: Integer arithmetic might be better than floating point
*/
tr = (double) addr->entry->timeouts / addr->entry->completed;
addr->entry->timeouts = addr->entry->completed = 0;
INSIST(addr->entry->atr >= 0.0);
INSIST(addr->entry->atr <= 1.0);
INSIST(adb->atr_discount >= 0.0);
INSIST(adb->atr_discount <= 1.0);
addr->entry->atr *= 1.0 - adb->atr_discount;
addr->entry->atr += tr * adb->atr_discount;
addr->entry->atr = ISC_CLAMP(addr->entry->atr, 0.0, 1.0);
if (addr->entry->atr < adb->atr_low && addr->entry->mode > 0) {
addr->entry->quota = adb->quota *
quota_adj[--addr->entry->mode] / 10000;
log_quota(addr->entry, "atr %0.2f, quota increased to %d",
addr->entry->atr, addr->entry->quota);
} else if (addr->entry->atr > adb->atr_high && addr->entry->mode < 99) {
addr->entry->quota = adb->quota *
quota_adj[++addr->entry->mode] / 10000;
log_quota(addr->entry, "atr %0.2f, quota decreased to %d",
addr->entry->atr, addr->entry->quota);
}
/* Ensure we don't drop to zero */
if (addr->entry->quota == 0)
addr->entry->quota = 1;
}
#define EDNSTOS 3U
isc_boolean_t
dns_adb_noedns(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
@@ -4153,6 +4299,7 @@ dns_adb_noedns(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
bucket = addr->entry->lock_bucket;
LOCK(&adb->entrylocks[bucket]);
if (addr->entry->edns == 0U &&
(addr->entry->plain > EDNSTOS || addr->entry->to4096 > EDNSTOS)) {
if (((addr->entry->plain + addr->entry->to4096) & 0x3f) != 0) {
@@ -4187,6 +4334,8 @@ dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
bucket = addr->entry->lock_bucket;
LOCK(&adb->entrylocks[bucket]);
maybe_adjust_quota(adb, addr, ISC_FALSE);
addr->entry->plain++;
if (addr->entry->plain == 0xff) {
addr->entry->edns >>= 1;
@@ -4209,6 +4358,9 @@ dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
bucket = addr->entry->lock_bucket;
LOCK(&adb->entrylocks[bucket]);
maybe_adjust_quota(adb, addr, ISC_TRUE);
/*
* If we have not had a successful query then clear all
* edns timeout information.
@@ -4224,6 +4376,7 @@ dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
addr->entry->to1432 >>= 1;
addr->entry->to4096 >>= 1;
}
addr->entry->plainto++;
if (addr->entry->plainto == 0xff) {
addr->entry->edns >>= 1;
@@ -4243,6 +4396,8 @@ dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) {
bucket = addr->entry->lock_bucket;
LOCK(&adb->entrylocks[bucket]);
maybe_adjust_quota(adb, addr, ISC_TRUE);
if (size <= 512U) {
if (addr->entry->to512 <= EDNSTOS) {
addr->entry->to512++;
@@ -4291,6 +4446,9 @@ dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) {
size = 512U;
if (size > addr->entry->udpsize)
addr->entry->udpsize = size;
maybe_adjust_quota(adb, addr, ISC_FALSE);
addr->entry->edns++;
if (addr->entry->edns == 0xff) {
addr->entry->edns >>= 1;
@@ -4618,3 +4776,53 @@ dns_adb_setadbsize(dns_adb_t *adb, size_t size) {
else
isc_mem_setwater(adb->mctx, water, adb, hiwater, lowater);
}
void
dns_adb_setquota(dns_adb_t *adb, isc_uint32_t quota, isc_uint32_t freq,
double low, double high, double discount)
{
REQUIRE(DNS_ADB_VALID(adb));
adb->quota = quota;
adb->atr_freq = freq;
adb->atr_low = low;
adb->atr_high = high;
adb->atr_discount = discount;
}
isc_boolean_t
dns_adbentry_overquota(dns_adbentry_t *entry) {
isc_boolean_t block;
REQUIRE(DNS_ADBENTRY_VALID(entry));
block = ISC_TF(entry->quota != 0 && entry->active >= entry->quota);
return (block);
}
void
dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
int bucket;
REQUIRE(DNS_ADB_VALID(adb));
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
bucket = addr->entry->lock_bucket;
LOCK(&adb->entrylocks[bucket]);
addr->entry->active++;
UNLOCK(&adb->entrylocks[bucket]);
}
void
dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) {
int bucket;
REQUIRE(DNS_ADB_VALID(adb));
REQUIRE(DNS_ADBADDRINFO_VALID(addr));
bucket = addr->entry->lock_bucket;
LOCK(&adb->entrylocks[bucket]);
if (addr->entry->active > 0)
addr->entry->active--;
UNLOCK(&adb->entrylocks[bucket]);
}