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:
226
lib/dns/adb.c
226
lib/dns/adb.c
@@ -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]);
|
||||
}
|
||||
|
Reference in New Issue
Block a user