Commit 55e5c51e authored by Evan Hunt's avatar Evan Hunt

[master] DNS RRL

3494.	[func]		DNS RRL: Blunt the impact of DNS reflection and
			amplification attacks by rate-limiting substantially-
			identical responses. [RT #28130]
parent 261ef379
3494. [func] DNS RRL: Blunt the impact of DNS reflection and
amplification attacks by rate-limiting substantially-
identical responses. [RT #28130]
3493. [contrib] Added BDBHPT dynamically-lodable DLZ module,
contributed by Mark Goldfinch. [RT #32549]
......
......@@ -1000,6 +1000,11 @@ client_send(ns_client_t *client) {
}
if (result != ISC_R_SUCCESS)
goto done;
/*
* Stop after the question if TC was set for rate limiting.
*/
if ((client->message->flags & DNS_MESSAGEFLAG_TC) != 0)
goto renderend;
result = dns_message_rendersection(client->message,
DNS_SECTION_ANSWER,
DNS_MESSAGERENDER_PARTIAL |
......@@ -1205,6 +1210,49 @@ ns_client_error(ns_client_t *client, isc_result_t result) {
}
#endif
/*
* Try to rate limit error responses.
*/
if (client->view != NULL && client->view->rrl != NULL) {
isc_boolean_t wouldlog;
char log_buf[DNS_RRL_LOG_BUF_LEN];
dns_rrl_result_t rrl_result;
INSIST(rcode != dns_rcode_noerror &&
rcode != dns_rcode_nxdomain);
wouldlog = (ns_g_server->log_queries &&
isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP));
rrl_result = dns_rrl(client->view, &client->peeraddr,
TCP_CLIENT(client),
dns_rdataclass_in, dns_rdatatype_none,
NULL, result, client->now,
wouldlog, log_buf, sizeof(log_buf));
if (rrl_result != DNS_RRL_RESULT_OK) {
/*
* Log dropped errors in the query category
* so that they are not lost in silence.
* Starts of rate-limited bursts are logged in
* NS_LOGCATEGORY_RRL.
*/
if (wouldlog) {
ns_client_log(client, NS_LOGCATEGORY_QUERIES,
NS_LOGMODULE_CLIENT,
DNS_RRL_LOG_DROP,
"%s", log_buf);
}
/*
* Some error responses cannot be 'slipped',
* so don't try.
* This will counted with dropped queries in the
* QryDropped counter.
*/
if (!client->view->rrl->log_only) {
ns_client_next(client, DNS_R_DROP);
return;
}
}
}
/*
* Message may be an in-progress reply that we had trouble
* with, in which case QR will be set. We need to clear QR before
......
......@@ -227,6 +227,13 @@ view \"_bind\" chaos {\n\
recursion no;\n\
notify no;\n\
allow-new-zones no;\n\
\n\
# Prevent use of this zone in DNS amplified reflection DoS attacks\n\
rate-limit {\n\
responses-per-second 3;\n\
slip 0;\n\
min-table-size 10;\n\
};\n\
\n\
zone \"version.bind\" chaos {\n\
type master;\n\
......
......@@ -85,6 +85,7 @@ struct ns_query {
#define NS_QUERYATTR_CACHEACLOK 0x2000
#define NS_QUERYATTR_DNS64 0x4000
#define NS_QUERYATTR_DNS64EXCLUDE 0x8000
#define NS_QUERYATTR_RRL_CHECKED 0x10000
isc_result_t
......
......@@ -169,7 +169,10 @@ enum {
dns_nsstatscounter_dns64 = 37,
dns_nsstatscounter_max = 38
dns_nsstatscounter_ratedropped = 38,
dns_nsstatscounter_rateslipped = 39,
dns_nsstatscounter_max = 40
};
void
......
......@@ -5857,6 +5857,105 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
resume:
CTRACE("query_find: resume");
/*
* Rate limit these responses to this client.
*/
if (client->view->rrl != NULL &&
fname != NULL && dns_name_isabsolute(fname) &&
(client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) {
dns_rdataset_t nc_rdataset;
isc_boolean_t wouldlog;
char log_buf[DNS_RRL_LOG_BUF_LEN];
isc_result_t nc_result;
dns_rrl_result_t rrl_result;
client->query.attributes |= NS_QUERYATTR_RRL_CHECKED;
wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP);
tname = fname;
if (result == DNS_R_NXDOMAIN) {
/*
* Use the database origin name to rate limit NXDOMAIN
*/
if (db != NULL)
tname = dns_db_origin(db);
rrl_result = result;
} else if (result == DNS_R_NCACHENXDOMAIN &&
rdataset != NULL &&
dns_rdataset_isassociated(rdataset) &&
(rdataset->attributes &
DNS_RDATASETATTR_NEGATIVE) != 0) {
/*
* Try to use owner name in the negative cache SOA.
*/
dns_fixedname_init(&fixed);
dns_rdataset_init(&nc_rdataset);
for (nc_result = dns_rdataset_first(rdataset);
nc_result == ISC_R_SUCCESS;
nc_result = dns_rdataset_next(rdataset)) {
dns_ncache_current(rdataset,
dns_fixedname_name(&fixed),
&nc_rdataset);
if (nc_rdataset.type == dns_rdatatype_soa) {
dns_rdataset_disassociate(&nc_rdataset);
tname = dns_fixedname_name(&fixed);
break;
}
dns_rdataset_disassociate(&nc_rdataset);
}
rrl_result = DNS_R_NXDOMAIN;
} else if (result == DNS_R_DELEGATION) {
rrl_result = result;
} else {
rrl_result = ISC_R_SUCCESS;
}
rrl_result = dns_rrl(client->view, &client->peeraddr,
ISC_TF((client->attributes
& NS_CLIENTATTR_TCP) != 0),
client->message->rdclass, qtype, tname,
rrl_result, client->now,
wouldlog, log_buf, sizeof(log_buf));
if (rrl_result != DNS_RRL_RESULT_OK) {
/*
* Log dropped or slipped responses in the query
* category so that requests are not silently lost.
* Starts of rate-limited bursts are logged in
* DNS_LOGCATEGORY_RRL.
*
* Dropped responses are counted with dropped queries
* in QryDropped while slipped responses are counted
* with other truncated responses in RespTruncated.
*/
if (wouldlog && ns_g_server->log_queries) {
ns_client_log(client, NS_LOGCATEGORY_QUERIES,
NS_LOGMODULE_CLIENT,
DNS_RRL_LOG_DROP,
"%s", log_buf);
}
if (!client->view->rrl->log_only) {
if (rrl_result == DNS_RRL_RESULT_DROP) {
/*
* These will also be counted in
* dns_nsstatscounter_dropped
*/
inc_stats(client,
dns_nsstatscounter_ratedropped);
QUERY_ERROR(DNS_R_DROP);
} else {
/*
* These will also be counted in
* dns_nsstatscounter_truncatedresp
*/
inc_stats(client,
dns_nsstatscounter_rateslipped);
client->message->flags |=
DNS_MESSAGEFLAG_TC;
}
goto cleanup;
}
}
}
if (!ISC_LIST_EMPTY(client->view->rpz_zones) &&
(RECURSIONOK(client) || !client->view->rpz_recursive_only) &&
rpz_ck_dnssec(client, result, rdataset, sigrdataset) &&
......@@ -7321,12 +7420,14 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
}
if (eresult != ISC_R_SUCCESS &&
(!PARTIALANSWER(client) || WANTRECURSION(client))) {
(!PARTIALANSWER(client) || WANTRECURSION(client)
|| eresult == DNS_R_DROP)) {
if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) {
/*
* This was a duplicate query that we are
* recursing on. Don't send a response now.
* The original query will still cause a response.
* recursing on or the result of rate limiting.
* Don't send a response now for a duplicate query,
* because the original will still cause a response.
*/
query_next(client, eresult);
} else {
......
......@@ -1678,6 +1678,199 @@ configure_rpz(dns_view_t *view, const cfg_listelt_t *element,
return (result);
}
#define CHECK_RRL(obj, cond, pat, val1, val2) \
do { \
if (!(cond)) { \
cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, \
pat, val1, val2); \
result = ISC_R_RANGE; \
goto cleanup; \
} \
} while (0)
static isc_result_t
configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
const cfg_obj_t *obj;
dns_rrl_t *rrl;
isc_result_t result;
int min_entries, i, j;
/*
* Most DNS servers have few clients, but intentinally open
* recursive and authoritative servers often have many.
* So start with a small number of entries unless told otherwise
* to reduce cold-start costs.
*/
min_entries = 500;
obj = NULL;
result = cfg_map_get(map, "min-table-size", &obj);
if (result == ISC_R_SUCCESS) {
min_entries = cfg_obj_asuint32(obj);
if (min_entries < 1)
min_entries = 1;
}
result = dns_rrl_init(&rrl, view, min_entries);
if (result != ISC_R_SUCCESS)
return (result);
i = ISC_MAX(20000, min_entries);
obj = NULL;
result = cfg_map_get(map, "max-table-size", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= min_entries,
"max-table-size %d < min-table-size %d",
i, min_entries);
}
rrl->max_entries = i;
i = 0;
obj = NULL;
result = cfg_map_get(map, "responses-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
"responses-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->responses_per_second = i;
rrl->scaled_responses_per_second = rrl->responses_per_second;
/*
* The default error rate is the response rate,
* and so off by default.
*/
i = rrl->responses_per_second;
obj = NULL;
result = cfg_map_get(map, "errors-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
"errors-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->errors_per_second = i;
rrl->scaled_errors_per_second = rrl->errors_per_second;
/*
* The default NXDOMAIN rate is the response rate,
* and so off by default.
*/
i = rrl->responses_per_second;
obj = NULL;
result = cfg_map_get(map, "nxdomains-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
"nxdomains-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->nxdomains_per_second = i;
rrl->scaled_nxdomains_per_second = rrl->nxdomains_per_second;
/*
* The all-per-second rate is off by default.
*/
i = 0;
obj = NULL;
result = cfg_map_get(map, "all-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE, "all-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->all_per_second = i;
rrl->scaled_all_per_second = rrl->all_per_second;
i = 2;
obj = NULL;
result = cfg_map_get(map, "slip", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_SLIP,
"slip %d > %d", i, DNS_RRL_MAX_SLIP);
}
rrl->slip = i;
rrl->scaled_slip = rrl->slip;
i = 15;
obj = NULL;
result = cfg_map_get(map, "window", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 1 && i <= DNS_RRL_MAX_WINDOW,
"window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW);
}
rrl->window = i;
i = 0;
obj = NULL;
result = cfg_map_get(map, "qps-scale", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 1, "invalid 'qps-scale %d'%s", i, "");
}
rrl->qps_scale = i;
rrl->qps = 1.0;
i = 24;
obj = NULL;
result = cfg_map_get(map, "IPv4-prefix-length", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 8 && i <= 32,
"invalid 'IPv4-prefix-length %d'%s", i, "");
}
rrl->ipv4_prefixlen = i;
if (i == 32)
rrl->ipv4_mask = 0xffffffff;
else
rrl->ipv4_mask = htonl(0xffffffff << (32-i));
i = 56;
obj = NULL;
result = cfg_map_get(map, "IPv6-prefix-length", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 16 && i <= DNS_RRL_MAX_PREFIX,
"IPv6-prefix-length %d < 16 or > %d",
i, DNS_RRL_MAX_PREFIX);
}
rrl->ipv6_prefixlen = i;
for (j = 0; j < 4; ++j) {
if (i <= 0) {
rrl->ipv6_mask[j] = 0;
} else if (i < 32) {
rrl->ipv6_mask[j] = htonl(0xffffffff << (32-i));
} else {
rrl->ipv6_mask[j] = 0xffffffff;
}
i -= 32;
}
obj = NULL;
result = cfg_map_get(map, "exempt-clients", &obj);
if (result == ISC_R_SUCCESS) {
result = cfg_acl_fromconfig(obj, config, ns_g_lctx,
ns_g_aclconfctx, ns_g_mctx,
0, &rrl->exempt);
CHECK_RRL(obj, result == ISC_R_SUCCESS,
"invalid %s%s", "address match list", "");
}
obj = NULL;
result = cfg_map_get(map, "log-only", &obj);
if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj))
rrl->log_only = ISC_TRUE;
else
rrl->log_only = ISC_FALSE;
return (ISC_R_SUCCESS);
cleanup:
dns_rrl_view_destroy(view);
return (result);
}
/*
* Configure 'view' according to 'vconfig', taking defaults from 'config'
* where values are missing in 'vconfig'.
......@@ -3100,6 +3293,14 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
}
}
obj = NULL;
result = ns_config_get(maps, "rate-limit", &obj);
if (result == ISC_R_SUCCESS) {
result = configure_rrl(view, config, obj);
if (result != ISC_R_SUCCESS)
goto cleanup;
}
result = ISC_R_SUCCESS;
cleanup:
......
......@@ -207,6 +207,10 @@ init_desc(void) {
SET_NSSTATDESC(recursclients, "recursing clients",
"RecursClients");
SET_NSSTATDESC(dns64, "queries answered by DNS64", "DNS64");
SET_NSSTATDESC(ratedropped, "responses dropped for rate limits",
"RateDropped");
SET_NSSTATDESC(rateslipped, "responses truncated for rate limits",
"RateSlipped");
INSIST(i == dns_nsstatscounter_max);
/* Initialize resolver statistics */
......
......@@ -17,6 +17,7 @@ involving a different DNS setup. They are:
nsupdate/ Dynamic update and IXFR tests
resolver/ Regression tests for resolver bugs that have been fixed
(not a complete resolver test suite)
rrl/ query rate limiting
rpz/ Tests of response policy zone (RPZ) rewriting
stub/ Tests of stub zone functionality
unknown/ Unknown type and class tests
......
......@@ -61,7 +61,7 @@ SUBDIRS="acl additional allow_query addzone autosign builtin
dsdigest ecdsa formerr forward glue gost ixfr inline limits
logfileconfig lwresd masterfile masterformat metadata
notify nsupdate pending pkcs11 redirect resolver rndc rpz
rrsetorder rsabigexponent sortlist smartsign staticstub
rrl rrsetorder rsabigexponent sortlist smartsign staticstub
statistics stub tkey tsig tsiggss unknown upforwd verify
views wildcard xfer xferquota zonechecks"
......
# Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# Clean up after rrl tests.
rm -f dig.out*
rm -f */named.memstats */named.run */named.stats */log */session.key
rm -f ns3/bl*.db */*.jnl */*.core */*.pid
/*
* Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
controls { /* empty */ };
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port 5300;
session-keyfile "session.key";
pid-file "named.pid";
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
notify no;
};
zone "." {type master; file "root.db";};
; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
;
; Permission to use, copy, modify, and/or distribute this software for any
; purpose with or without fee is hereby granted, provided that the above
; copyright notice and this permission notice appear in all copies.
;
; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
; PERFORMANCE OF THIS SOFTWARE.
$TTL 120
@ SOA ns. hostmaster.ns. ( 1 3600 1200 604800 60 )
@ NS ns.
ns. A 10.53.0.1
. A 10.53.0.1
; limit responses from here
tld2. NS ns.tld2.
ns.tld2. A 10.53.0.2
; limit recursion to here
tld3. NS ns.tld3.
ns.tld3. A 10.53.0.3
; generate SERVFAIL
tld4. NS ns.tld3.
; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
;
; Permission to use, copy, modify, and/or distribute this software for any
; purpose with or without fee is hereby granted, provided that the above
; copyright notice and this permission notice appear in all copies.
;
; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
; PERFORMANCE OF THIS SOFTWARE.
. 0 NS ns1.
ns1. 0 A 10.53.0.1
/*
* Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
controls { /* empty */ };
options {
query-source address 10.53.0.2;
notify-source 10.53.0.2;
transfer-source 10.53.0.2;
port 5300;
session-keyfile "session.key";
pid-file "named.pid";
statistics-file "named.stats";
listen-on { 10.53.0.2; };
listen-on-v6 { none; };
notify no;
rate-limit {
responses-per-second 2;
all-per-second 70;
IPv4-prefix-length 24;
IPv6-prefix-length 64;
slip 3;
/* qps-scale 2; */
exempt-clients { 10.53.0.7; };
window 1;
max-table-size 100;
min-table-size 2;
};
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-md5;
};