Commit 94315060 authored by Evan Hunt's avatar Evan Hunt

[master] RPZ speedup (phase 2, multiple RPZ's)

3495.	[func]		Support multiple response-policy zones, while
			improving RPZ performance. [RT #32476]
parent 55e5c51e
3495. [func] Support multiple response-policy zones, while
improving RPZ performance. [RT #32476]
3494. [func] DNS RRL: Blunt the impact of DNS reflection and
amplification attacks by rate-limiting substantially-
identical responses. [RT #28130]
......
......@@ -172,7 +172,9 @@ enum {
dns_nsstatscounter_ratedropped = 38,
dns_nsstatscounter_rateslipped = 39,
dns_nsstatscounter_max = 40
dns_nsstatscounter_rpz_rewrites = 40,
dns_nsstatscounter_max = 41
};
void
......
This diff is collapsed.
......@@ -369,7 +369,8 @@ configure_alternates(const cfg_obj_t *config, dns_view_t *view,
static isc_result_t
configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view,
cfg_aclconfctx_t *aclconf, isc_boolean_t added);
cfg_aclconfctx_t *aclconf, isc_boolean_t added,
isc_boolean_t old_rpz_ok);
static isc_result_t
add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx);
......@@ -1557,47 +1558,78 @@ cleanup:
}
static isc_result_t
configure_rpz(dns_view_t *view, const cfg_listelt_t *element,
isc_boolean_t recursive_only_def, dns_ttl_t ttl_def)
configure_rpz_name(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name,
const char *str, const char *msg)
{
const cfg_obj_t *rpz_obj, *policy_obj, *obj;
isc_result_t result;
result = dns_name_fromstring(name, str, DNS_NAME_DOWNCASE, view->mctx);
if (result != ISC_R_SUCCESS)
cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"invalid %s '%s'", msg, str);
return (result);
}
static isc_result_t
configure_rpz_name2(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name,
const char *str, const dns_name_t *origin)
{
isc_result_t result;
result = dns_name_fromstring2(name, str, origin, DNS_NAME_DOWNCASE,
view->mctx);
if (result != ISC_R_SUCCESS)
cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"invalid zone '%s'", str);
return (result);
}
static isc_result_t
configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element,
isc_boolean_t recursive_only_def, dns_ttl_t ttl_def,
const dns_rpz_zone_t *old, isc_boolean_t *old_rpz_okp)
{
const cfg_obj_t *rpz_obj, *obj;
const char *str;
dns_rpz_zone_t *old, *new;
dns_zone_t *zone = NULL;
dns_rpz_zone_t *new;
isc_result_t result;
dns_rpz_num_t rpz_num;
REQUIRE(old != NULL || !*old_rpz_okp);
rpz_obj = cfg_listelt_value(element);
if (view->rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES)
return (ISC_R_NOMEMORY);
new = isc_mem_get(view->mctx, sizeof(*new));
new = isc_mem_get(view->rpzs->mctx, sizeof(*new));
if (new == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"no memory for response policy zones");
return (ISC_R_NOMEMORY);
}
memset(new, 0, sizeof(*new));
result = isc_refcount_init(&new->refs, 1);
if (result != ISC_R_SUCCESS) {
isc_mem_put(view->rpzs->mctx, new, sizeof(*new));
return (result);
}
dns_name_init(&new->origin, NULL);
dns_name_init(&new->ip, NULL);
dns_name_init(&new->nsdname, NULL);
dns_name_init(&new->cname, NULL);
dns_name_init(&new->nsip, NULL);
dns_name_init(&new->passthru, NULL);
ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link);
rpz_obj = cfg_listelt_value(element);
policy_obj = cfg_tuple_get(rpz_obj, "policy");
if (cfg_obj_isvoid(policy_obj)) {
new->policy = DNS_RPZ_POLICY_GIVEN;
} else {
str = cfg_obj_asstring(cfg_tuple_get(policy_obj,
"policy name"));
new->policy = dns_rpz_str2policy(str);
INSIST(new->policy != DNS_RPZ_POLICY_ERROR);
}
dns_name_init(&new->cname, NULL);
new->num = view->rpzs->p.num_zones++;
view->rpzs->zones[new->num] = new;
obj = cfg_tuple_get(rpz_obj, "recursive-only");
if (cfg_obj_isvoid(obj)) {
new->recursive_only = recursive_only_def;
if (cfg_obj_isvoid(obj) ? recursive_only_def : cfg_obj_asboolean(obj)) {
view->rpzs->p.no_rd_ok &= ~DNS_RPZ_ZBIT(new->num);
} else {
new->recursive_only = cfg_obj_asboolean(obj);
view->rpzs->p.no_rd_ok |= DNS_RPZ_ZBIT(new->num);
}
if (!new->recursive_only)
view->rpz_recursive_only = ISC_FALSE;
obj = cfg_tuple_get(rpz_obj, "max-policy-ttl");
if (cfg_obj_isuint32(obj)) {
......@@ -1605,77 +1637,177 @@ configure_rpz(dns_view_t *view, const cfg_listelt_t *element,
} else {
new->max_policy_ttl = ttl_def;
}
if (*old_rpz_okp && new->max_policy_ttl != old->max_policy_ttl)
*old_rpz_okp = ISC_FALSE;
str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name"));
result = dns_name_fromstring(&new->origin, str, DNS_NAME_DOWNCASE,
view->mctx);
if (result != ISC_R_SUCCESS) {
result = configure_rpz_name(view, rpz_obj, &new->origin, str, "zone");
if (result != ISC_R_SUCCESS)
return (result);
if (dns_name_equal(&new->origin, dns_rootname)) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"invalid zone '%s'", str);
goto cleanup;
"invalid zone name '%s'", str);
return (DNS_R_EMPTYLABEL);
}
result = dns_name_fromstring2(&new->nsdname, DNS_RPZ_NSDNAME_ZONE,
&new->origin, DNS_NAME_DOWNCASE,
view->mctx);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"invalid zone '%s'", str);
goto cleanup;
for (rpz_num = 0; rpz_num < view->rpzs->p.num_zones-1; ++rpz_num) {
if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin,
&new->origin)) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"duplicate '%s'", str);
result = DNS_R_DUPLICATE;
return (result);
}
}
if (*old_rpz_okp && !dns_name_equal(&old->origin, &new->origin))
*old_rpz_okp = ISC_FALSE;
result = dns_name_fromstring(&new->passthru, DNS_RPZ_PASSTHRU_ZONE,
DNS_NAME_DOWNCASE, view->mctx);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"invalid zone '%s'", str);
goto cleanup;
}
result = configure_rpz_name2(view, rpz_obj, &new->ip,
DNS_RPZ_IP_ZONE, &new->origin);
if (result != ISC_R_SUCCESS)
return (result);
result = dns_view_findzone(view, &new->origin, &zone);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"unknown zone '%s'", str);
goto cleanup;
}
if (dns_zone_gettype(zone) != dns_zone_master &&
dns_zone_gettype(zone) != dns_zone_slave) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"zone '%s' is neither master nor slave", str);
dns_zone_detach(&zone);
result = DNS_R_NOTMASTER;
goto cleanup;
}
dns_zone_detach(&zone);
result = configure_rpz_name2(view, rpz_obj, &new->nsdname,
DNS_RPZ_NSDNAME_ZONE, &new->origin);
if (result != ISC_R_SUCCESS)
return (result);
for (old = ISC_LIST_HEAD(view->rpz_zones);
old != new;
old = ISC_LIST_NEXT(old, link)) {
++new->num;
if (dns_name_equal(&old->origin, &new->origin)) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"duplicate '%s'", str);
result = DNS_R_DUPLICATE;
goto cleanup;
result = configure_rpz_name2(view, rpz_obj, &new->nsip,
DNS_RPZ_NSIP_ZONE, &new->origin);
if (result != ISC_R_SUCCESS)
return (result);
result = configure_rpz_name(view, rpz_obj, &new->passthru,
DNS_RPZ_PASSTHRU_ZONE, "zone");
if (result != ISC_R_SUCCESS)
return (result);
obj = cfg_tuple_get(rpz_obj, "policy");
if (cfg_obj_isvoid(obj)) {
new->policy = DNS_RPZ_POLICY_GIVEN;
} else {
str = cfg_obj_asstring(cfg_tuple_get(obj, "policy name"));
new->policy = dns_rpz_str2policy(str);
INSIST(new->policy != DNS_RPZ_POLICY_ERROR);
if (new->policy == DNS_RPZ_POLICY_CNAME) {
str = cfg_obj_asstring(cfg_tuple_get(obj, "cname"));
result = configure_rpz_name(view, rpz_obj, &new->cname,
str, "cname");
if (result != ISC_R_SUCCESS)
return (result);
}
}
if (*old_rpz_okp && (new->policy != old->policy ||
!dns_name_equal(&old->cname, &new->cname)))
*old_rpz_okp = ISC_FALSE;
return (ISC_R_SUCCESS);
}
if (new->policy == DNS_RPZ_POLICY_CNAME) {
str = cfg_obj_asstring(cfg_tuple_get(policy_obj, "cname"));
result = dns_name_fromstring(&new->cname, str,
DNS_NAME_DOWNCASE, view->mctx);
static isc_result_t
configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj,
isc_boolean_t *old_rpz_okp)
{
const cfg_listelt_t *zone_element;
const cfg_obj_t *sub_obj;
isc_boolean_t recursive_only_def;
dns_ttl_t ttl_def;
dns_rpz_zones_t *new;
const dns_rpz_zones_t *old;
dns_view_t *pview;
const dns_rpz_zone_t *old_zone;
isc_result_t result;
int i;
*old_rpz_okp = ISC_FALSE;
zone_element = cfg_list_first(cfg_tuple_get(rpz_obj, "zone list"));
if (zone_element == NULL)
return (ISC_R_SUCCESS);
result = dns_rpz_new_zones(&view->rpzs, view->mctx);
if (result != ISC_R_SUCCESS)
return (result);
new = view->rpzs;
sub_obj = cfg_tuple_get(rpz_obj, "recursive-only");
if (!cfg_obj_isvoid(sub_obj) &&
!cfg_obj_asboolean(sub_obj))
recursive_only_def = ISC_FALSE;
else
recursive_only_def = ISC_TRUE;
sub_obj = cfg_tuple_get(rpz_obj, "break-dnssec");
if (!cfg_obj_isvoid(sub_obj) &&
cfg_obj_asboolean(sub_obj))
new->p.break_dnssec = ISC_TRUE;
else
new->p.break_dnssec = ISC_FALSE;
sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl");
if (cfg_obj_isuint32(sub_obj))
ttl_def = cfg_obj_asuint32(sub_obj);
else
ttl_def = DNS_RPZ_MAX_TTL_DEFAULT;
sub_obj = cfg_tuple_get(rpz_obj, "min-ns-dots");
if (cfg_obj_isuint32(sub_obj))
new->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1;
else
new->p.min_ns_labels = 2;
sub_obj = cfg_tuple_get(rpz_obj, "qname-wait-recurse");
if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj))
new->p.qname_wait_recurse = ISC_TRUE;
else
new->p.qname_wait_recurse = ISC_FALSE;
pview = NULL;
result = dns_viewlist_find(&ns_g_server->viewlist,
view->name, view->rdclass, &pview);
if (result == ISC_R_SUCCESS) {
old = pview->rpzs;
} else {
old = NULL;
}
if (old == NULL)
*old_rpz_okp = ISC_FALSE;
else
*old_rpz_okp = ISC_TRUE;
for (i = 0;
zone_element != NULL;
++i, zone_element = cfg_list_next(zone_element)) {
if (*old_rpz_okp && i < old->p.num_zones) {
old_zone = old->zones[i];
} else {
*old_rpz_okp = ISC_FALSE;
old_zone = NULL;
}
result = configure_rpz_zone(view, zone_element,
recursive_only_def, ttl_def,
old_zone, old_rpz_okp);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"invalid cname '%s'", str);
goto cleanup;
if (pview != NULL)
dns_view_detach(&pview);
return (result);
}
}
return (ISC_R_SUCCESS);
/*
* If this is a reloading and the parameters and list of policy
* zones are unchanged, then use the same policy data.
* Data for individual zones that must be reloaded will be merged.
*/
if (old != NULL && memcmp(&old->p, &new->p, sizeof(new->p)) != 0)
*old_rpz_okp = ISC_FALSE;
if (*old_rpz_okp) {
dns_rpz_detach_rpzs(&view->rpzs);
dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs);
}
if (pview != NULL)
dns_view_detach(&pview);
cleanup:
dns_rpz_view_destroy(view);
return (result);
return (ISC_R_SUCCESS);
}
#define CHECK_RRL(obj, cond, pat, val1, val2) \
......@@ -1938,6 +2070,7 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL;
unsigned int query_timeout, ndisp;
struct cfg_context *nzctx;
isc_boolean_t old_rpz_ok = ISC_FALSE;
REQUIRE(DNS_VIEW_VALID(view));
......@@ -2035,6 +2168,16 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
&view->queryacl));
}
/*
* Make the list of response policy zone names for a view that
* is used for real lookups and so cares about hints.
*/
obj = NULL;
if (view->rdclass == dns_rdataclass_in && need_hints &&
ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) {
CHECK(configure_rpz(view, obj, &old_rpz_ok));
}
/*
* Configure the zones.
*/
......@@ -2053,7 +2196,30 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
{
const cfg_obj_t *zconfig = cfg_listelt_value(element);
CHECK(configure_zone(config, zconfig, vconfig, mctx, view,
actx, ISC_FALSE));
actx, ISC_FALSE, old_rpz_ok));
}
/*
* Check that a master or slave zone was found for each
* zone named in the response policy statement.
*/
if (view->rpzs != NULL) {
dns_rpz_num_t n;
for (n = 0; n < view->rpzs->p.num_zones; ++n)
{
if ((view->rpzs->defined & DNS_RPZ_ZBIT(n)) == 0) {
char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(&view->rpzs->zones[n]->origin,
namebuf, sizeof(namebuf));
cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
"'%s' is not a master or slave zone",
namebuf);
result = ISC_R_NOTFOUND;
goto cleanup;
}
}
}
/*
......@@ -2078,7 +2244,7 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
const cfg_obj_t *zconfig = cfg_listelt_value(element);
CHECK(configure_zone(config, zconfig, vconfig,
mctx, view, actx,
ISC_TRUE));
ISC_TRUE, ISC_FALSE));
}
}
......@@ -3250,49 +3416,6 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
}
}
/*
* Make the list of response policy zone names for views that
* are used for real lookups and so care about hints.
*/
obj = NULL;
if (view->rdclass == dns_rdataclass_in && need_hints &&
ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) {
const cfg_obj_t *recursive_only_obj;
const cfg_obj_t *break_dnssec_obj, *ttl_obj;
isc_boolean_t recursive_only_def;
dns_ttl_t ttl_def;
recursive_only_obj = cfg_tuple_get(obj, "recursive-only");
if (!cfg_obj_isvoid(recursive_only_obj) &&
!cfg_obj_asboolean(recursive_only_obj))
recursive_only_def = ISC_FALSE;
else
recursive_only_def = ISC_TRUE;
break_dnssec_obj = cfg_tuple_get(obj, "break-dnssec");
if (!cfg_obj_isvoid(break_dnssec_obj) &&
cfg_obj_asboolean(break_dnssec_obj))
view->rpz_break_dnssec = ISC_TRUE;
else
view->rpz_break_dnssec = ISC_FALSE;
ttl_obj = cfg_tuple_get(obj, "max-policy-ttl");
if (cfg_obj_isuint32(ttl_obj))
ttl_def = cfg_obj_asuint32(ttl_obj);
else
ttl_def = DNS_RPZ_MAX_TTL_DEFAULT;
for (element = cfg_list_first(cfg_tuple_get(obj, "zone list"));
element != NULL;
element = cfg_list_next(element)) {
result = configure_rpz(view, element,
recursive_only_def, ttl_def);
if (result != ISC_R_SUCCESS)
goto cleanup;
dns_rpz_set_need(ISC_TRUE);
}
}
obj = NULL;
result = ns_config_get(maps, "rate-limit", &obj);
if (result == ISC_R_SUCCESS) {
......@@ -3633,7 +3756,8 @@ create_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist,
static isc_result_t
configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view,
cfg_aclconfctx_t *aclconf, isc_boolean_t added)
cfg_aclconfctx_t *aclconf, isc_boolean_t added,
isc_boolean_t old_rpz_ok)
{
dns_view_t *pview = NULL; /* Production view */
dns_zone_t *zone = NULL; /* New or reused zone */
......@@ -3654,6 +3778,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
const char *zname;
dns_rdataclass_t zclass;
const char *ztypestr;
dns_rpz_num_t rpz_num;
options = NULL;
(void)cfg_map_get(config, "options", &options);
......@@ -3814,6 +3939,18 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
}
INSIST(dupzone == NULL);
/*
* Note whether this is a response policy zone and which one if so.
*/
for (rpz_num = 0; ; ++rpz_num) {
if (view->rpzs == NULL || rpz_num >= view->rpzs->p.num_zones) {
rpz_num = DNS_RPZ_INVALID_NUM;
break;
}
if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin))
break;
}
/*
* See if we can reuse an existing zone. This is
* only possible if all of these are true:
......@@ -3822,6 +3959,9 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
* - The zone is compatible with the config
* options (e.g., an existing master zone cannot
* be reused if the options specify a slave zone)
* - The zone was not and is still not a response policy zone
* or the zone is a policy zone with an unchanged number
* and we are using the old policy zone summary data.
*/
result = dns_viewlist_find(&ns_g_server->viewlist, view->name,
view->rdclass, &pview);
......@@ -3835,6 +3975,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
if (zone != NULL && !ns_zone_reusable(zone, zconfig))
dns_zone_detach(&zone);
if (zone != NULL && (rpz_num != dns_zone_get_rpz_num(zone) ||
(rpz_num != DNS_RPZ_INVALID_NUM && !old_rpz_ok)))
dns_zone_detach(&zone);
if (zone != NULL) {
/*
* We found a reusable zone. Make it use the
......@@ -3857,6 +4001,19 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
dns_zone_setstats(zone, ns_g_server->zonestats);
}
if (rpz_num != DNS_RPZ_INVALID_NUM) {
result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num);
if (result != ISC_R_SUCCESS) {
isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
"zone '%s': incompatible"
" masterfile-format or database"
" for a response policy zone",
zname);
goto cleanup;
}
}
/*
* If the zone contains a 'forwarders' statement, configure
* selective forwarding.
......@@ -8057,7 +8214,8 @@ ns_server_add_zone(ns_server_t *server, char *args) {
isc_task_beginexclusive(server->task);
dns_view_thaw(view);
result = configure_zone(cfg->config, parms, vconfig,
server->mctx, view, cfg->actx, ISC_FALSE);
server->mctx, view, cfg->actx, ISC_FALSE,
ISC_FALSE);
dns_view_freeze(view);
isc_task_endexclusive(server->task);
if (result != ISC_R_SUCCESS)
......
......@@ -211,6 +211,8 @@ init_desc(void) {
"RateDropped");
SET_NSSTATDESC(rateslipped, "responses truncated for rate limits",
"RateSlipped");
SET_NSSTATDESC(rpz_rewrites, "response policy zone rewrites",
"RPZRewrites");
INSIST(i == dns_nsstatscounter_max);
/* Initialize resolver statistics */
......
# Copyright (C) 2011, 2012 Internet Systems Consortium, Inc. ("ISC")
# Copyright (C) 2011-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
......@@ -12,7 +12,6 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# $Id: Makefile.in,v 1.3 2011/01/13 04:59:24 tbox Exp $
srcdir = @srcdir@
VPATH = @srcdir@
......
# Copyright (C) 2011, 2012 Internet Systems Consortium, Inc. ("ISC")