diff --git a/bin/delv/delv.c b/bin/delv/delv.c index 549d1bde5a..acd909965b 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -88,6 +88,9 @@ #define MAXNAME (DNS_NAME_MAXTEXT + 1) +#define MAX_QUERIES 32 +#define MAX_RESTARTS 11 + /* Variables used internally by delv. */ char *progname = NULL; static isc_mem_t *mctx = NULL; @@ -130,6 +133,9 @@ static bool showcomments = true, showdnssec = true, showtrust = true, multiline = false, short_form = false, print_unknown_format = false, yaml = false, fulltrace = false; +static uint32_t maxqueries = MAX_QUERIES; +static uint32_t restarts = MAX_RESTARTS; + static bool resolve_trace = false, validator_trace = false, message_trace = false, send_trace = false; @@ -1191,6 +1197,23 @@ plus_option(char *option) { break; case 'm': switch (cmd[1]) { + case 'a': /* maxqueries */ + FULLCHECK("maxqueries"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&maxqueries, value, UINT_MAX, + "maxqueries"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse maxqueries"); + } + if (maxqueries == 0) { + fatal("maxqueries must be nonzero"); + } + break; case 't': /* mtrace */ FULLCHECK("mtrace"); message_trace = state; @@ -1243,6 +1266,22 @@ plus_option(char *option) { break; case 'r': switch (cmd[1]) { + case 'e': /* restarts */ + FULLCHECK("restarts"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&restarts, value, 255, "restarts"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse restarts"); + } + if (restarts == 0) { + fatal("restarts must be between 1..255"); + } + break; case 'o': /* root */ FULLCHECK("root"); if (state && no_sigs) { @@ -1370,10 +1409,7 @@ plus_option(char *option) { break; default: invalid_option: - /* - * We can also add a "need_value:" case here if we ever - * add a plus-option that requires a specified value - */ + need_value: fprintf(stderr, "Invalid option: +%s\n", option); usage(); } @@ -1898,6 +1934,7 @@ run_resolve(void *arg) { /* Create client */ CHECK(dns_client_create(mctx, loopmgr, netmgr, 0, tlsctx_client_cache, &client, srcaddr4, srcaddr6)); + dns_client_setmaxrestarts(client, restarts); /* Set the nameserver */ if (server != NULL) { @@ -2162,6 +2199,7 @@ run_server(void *arg) { dns_view_setcache(view, cache, false); dns_cache_detach(&cache); dns_view_setdstport(view, destport); + dns_view_setmaxrestarts(view, restarts); CHECK(dns_rootns_create(mctx, dns_rdataclass_in, hintfile, &roothints)); dns_view_sethints(view, roothints); @@ -2175,6 +2213,7 @@ run_server(void *arg) { CHECK(dns_view_createresolver(view, netmgr, 0, tlsctx_client_cache, dispatch, NULL)); + dns_resolver_setmaxqueries(view->resolver, maxqueries); isc_stats_create(mctx, &resstats, dns_resstatscounter_max); dns_resolver_setstats(view->resolver, resstats); diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 2ab1f897f4..74239c9bc1 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -337,6 +337,18 @@ assign values to options like the timeout interval. They have the form they are replaced by the string ``[omitted]`` or, in the DNSKEY case, the key ID is displayed as the replacement, e.g. ``[ key id = value ]``. +.. option:: +restarts + + When name server mode (``delv +ns``) is in use, this option sets the + maximum number of CNAME queries to follow before terminating resolution. + This prevents ``delv`` from hanging in the event of a CNAME loop. + The default is 11. + +.. option:: +maxqueries + + This option specifies the maximum number of queries to send to resolve + a name before giving up. The default is 32. + .. option:: +trust, +notrust This option controls whether to display the trust level when printing a record. diff --git a/bin/named/config.c b/bin/named/config.c index 3ad700a887..d0f70bddbc 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -170,7 +170,8 @@ options {\n\ max-clients-per-query 100;\n\ max-ncache-ttl 10800; /* 3 hours */\n\ max-recursion-depth 7;\n\ - max-recursion-queries 100;\n\ + max-recursion-queries 32;\n\ + max-query-restarts 11;\n\ max-stale-ttl 86400; /* 1 day */\n\ message-compression yes;\n\ min-ncache-ttl 0; /* 0 hours */\n\ diff --git a/bin/named/server.c b/bin/named/server.c index 35d4693732..338f885bd8 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -5502,6 +5502,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, INSIST(result == ISC_R_SUCCESS); dns_resolver_setmaxqueries(view->resolver, cfg_obj_asuint32(obj)); + obj = NULL; + result = named_config_get(maps, "max-query-restarts", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_view_setmaxrestarts(view, cfg_obj_asuint32(obj)); + obj = NULL; result = named_config_get(maps, "max-validations-per-fetch", &obj); if (result == ISC_R_SUCCESS) { diff --git a/bin/tests/system/chain/ns7/named.conf.in b/bin/tests/system/chain/ns7/named.conf.in index 87931179d2..0d95bc88c8 100644 --- a/bin/tests/system/chain/ns7/named.conf.in +++ b/bin/tests/system/chain/ns7/named.conf.in @@ -37,11 +37,28 @@ key rndc_key { algorithm @DEFAULT_HMAC@; }; +key restart16 { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + controls { inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; -zone "." { - type hint; - file "root.hint"; +view restart16 { + match-clients { key restart16; none; }; + max-query-restarts 16; + + zone "." { + type hint; + file "root.hint"; + }; +}; + +view default { + zone "." { + type hint; + file "root.hint"; + }; }; diff --git a/bin/tests/system/chain/tests.sh b/bin/tests/system/chain/tests.sh index 2c87e123e4..7128b6cd8e 100644 --- a/bin/tests/system/chain/tests.sh +++ b/bin/tests/system/chain/tests.sh @@ -442,11 +442,13 @@ n=$((n + 1)) echo_i "checking CNAME loops are detected (resolver) ($n)" ret=0 $RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i -$DIG $DIGOPTS @10.53.0.7 loop.example >dig.out.test$n -grep "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1 -grep "ANSWER: 0" dig.out.test$n >/dev/null || ret=1 -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status + ret)) +$DIG $DIGOPTS @10.53.0.7 loop.example >dig.out.1.test$n +grep "status: NOERROR" dig.out.1.test$n >/dev/null || ret=1 +grep "ANSWER: 12" dig.out.1.test$n >/dev/null || ret=1 +# also check with max-query-restarts 16: +$DIG $DIGOPTS @10.53.0.7 -y "${DEFAULT_HMAC}:restart16:1234abcd8765" loop.example >dig.out.2.test$n +grep "status: NOERROR" dig.out.2.test$n >/dev/null || ret=1 +grep "ANSWER: 17" dig.out.2.test$n >/dev/null || ret=1 n=$((n + 1)) echo_i "checking CNAME loops are detected (auth) ($n)" @@ -454,7 +456,7 @@ ret=0 $DIG $DIGOPTS @10.53.0.2 loop.example >dig.out.test$n grep "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1 grep "max. restarts reached" dig.out.test$n >/dev/null || ret=1 -grep "ANSWER: 17" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 12" dig.out.test$n >/dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) diff --git a/bin/tests/system/checkconf/good.conf.in b/bin/tests/system/checkconf/good.conf.in index 076ecc432d..e326f7ce74 100644 --- a/bin/tests/system/checkconf/good.conf.in +++ b/bin/tests/system/checkconf/good.conf.in @@ -81,6 +81,7 @@ options { check-names primary warn; check-names secondary ignore; max-cache-size 20000000000000; + max-query-restarts 10; nta-lifetime 604800; nta-recheck 604800; validate-except { @@ -112,6 +113,7 @@ view "first" { max-ixfr-ratio unlimited; }; dnssec-validation auto; + max-query-restarts 15; zone-statistics terse; }; view "second" { diff --git a/bin/tests/system/reclimit/ns3/named1.conf.in b/bin/tests/system/reclimit/ns3/named1.conf.in index 7771d834a2..295eae0864 100644 --- a/bin/tests/system/reclimit/ns3/named1.conf.in +++ b/bin/tests/system/reclimit/ns3/named1.conf.in @@ -22,6 +22,7 @@ options { listen-on-v6 { none; }; servfail-ttl 0; qname-minimization disabled; + max-recursion-queries 50; max-recursion-depth 12; recursion yes; dnssec-validation yes; diff --git a/bin/tests/system/resolver/ns1/named.conf.in b/bin/tests/system/resolver/ns1/named.conf.in index d212869fa3..3352b760ba 100644 --- a/bin/tests/system/resolver/ns1/named.conf.in +++ b/bin/tests/system/resolver/ns1/named.conf.in @@ -30,6 +30,7 @@ options { max-zone-ttl unlimited; resolver-query-timeout 5000; # 5 seconds attach-cache "globalcache"; + max-recursion-queries 50; }; trust-anchors { }; diff --git a/bin/tests/system/resolver/tests.sh b/bin/tests/system/resolver/tests.sh index 0e6db14457..d718362e01 100755 --- a/bin/tests/system/resolver/tests.sh +++ b/bin/tests/system/resolver/tests.sh @@ -149,7 +149,7 @@ dig_with_opts +tcp longcname1.example.net @10.53.0.1 a >dig.out.ns1.test${n} || grep -F "status: SERVFAIL" dig.out.ns1.test${n} >/dev/null || ret=1 grep -F "max. restarts reached" dig.out.ns1.test${n} >/dev/null || ret=1 lines=$(grep -F "CNAME" dig.out.ns1.test${n} | wc -l) -test ${lines:-1} -eq 17 || ret=1 +test ${lines:-1} -eq 12 || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index a1b3e2e2a7..0a3075edc2 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -4594,9 +4594,20 @@ Tuning :tags: server, query :short: Sets the maximum number of iterative queries while servicing a recursive query. - This sets the maximum number of iterative queries that may be sent while - servicing a recursive query. If more queries are sent, the recursive - query is terminated and returns SERVFAIL. The default is 100. + This sets the maximum number of iterative queries that may be sent + by a resolver while looking up a single name. If more queries than this + need to be sent before an answer is reached, then recursion is terminated + and a SERVFAIL response is returned to the client. (Note: if the answer + is a CNAME, then the subsequent lookup for the target of the CNAME is + counted separately.) The default is 32. + +.. namedconf:statement:: max-query-restarts + :tags: server, query + :short: Sets the maximum number of chained CNAMEs to follow + + This sets the maximum number of successive CNAME targets to follow + when resolving a client query, before terminating the query to avoid a + CNAME loop. Valid values are 1 to 255. The default is 11. .. namedconf:statement:: notify-delay :tags: transfer, zone diff --git a/doc/misc/options b/doc/misc/options index 5e5f808704..ff3c95ddf6 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -182,6 +182,7 @@ options { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; + max-query-restarts ; max-records ; max-records-per-type ; max-recursion-depth ; @@ -471,6 +472,7 @@ view [ ] { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; + max-query-restarts ; max-records ; max-records-per-type ; max-recursion-depth ; diff --git a/lib/dns/client.c b/lib/dns/client.c index f0b0311b37..bcd9e4c3e5 100644 --- a/lib/dns/client.c +++ b/lib/dns/client.c @@ -57,8 +57,6 @@ #define UCTX_MAGIC ISC_MAGIC('U', 'c', 't', 'x') #define UCTX_VALID(c) ISC_MAGIC_VALID(c, UCTX_MAGIC) -#define MAX_RESTARTS 16 - #define CHECK(r) \ do { \ result = (r); \ @@ -81,6 +79,7 @@ struct dns_client { unsigned int find_timeout; unsigned int find_udpretries; + uint8_t max_restarts; isc_refcount_t references; @@ -90,6 +89,7 @@ struct dns_client { #define DEF_FIND_TIMEOUT 5 #define DEF_FIND_UDPRETRIES 3 +#define DEF_MAX_RESTARTS 11 /*% * Internal state for a single name resolution procedure @@ -250,6 +250,7 @@ dns_client_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, isc_nm_t *nm, *client = (dns_client_t){ .loop = isc_loop_get(loopmgr, 0), .nm = nm, + .max_restarts = DEF_MAX_RESTARTS, }; result = dns_dispatchmgr_create(mctx, loopmgr, nm, @@ -384,6 +385,14 @@ dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass, return (result); } +void +dns_client_setmaxrestarts(dns_client_t *client, uint8_t max_restarts) { + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(max_restarts > 0); + + client->max_restarts = max_restarts; +} + static isc_result_t getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { dns_rdataset_t *rdataset; @@ -778,7 +787,9 @@ client_resfind(resctx_t *rctx, dns_fetchresponse_t *resp) { /* * Limit the number of restarts. */ - if (want_restart && rctx->restarts == MAX_RESTARTS) { + if (want_restart && + rctx->restarts == rctx->client->max_restarts) + { want_restart = false; result = ISC_R_QUOTA; send_event = true; diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h index 148578f4b7..c065c0c705 100644 --- a/lib/dns/include/dns/client.h +++ b/lib/dns/include/dns/client.h @@ -174,6 +174,19 @@ dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass, *\li Anything else Failure. */ +void +dns_client_setmaxrestarts(dns_client_t *client, uint8_t max_restarts); +/*%< + * Set the number of permissible chained queries before we give up, + * to prevent CNAME loops. This defaults to 11. + * + * Requires: + * + *\li 'client' is a valid client. + + *\li 'max_restarts' is greater than 0. + */ + typedef void (*dns_client_resolve_cb)(dns_client_t *client, const dns_name_t *name, dns_namelist_t *namelist, diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index c68c5555b9..02058d115b 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -146,12 +146,13 @@ struct dns_validator { unsigned int authfail; isc_stdtime_t start; - bool digest_sha1; - bool supported_algorithm; - dns_rdata_t rdata; - bool resume; - uint32_t *nvalidations; - uint32_t *nfails; + bool digest_sha1; + bool supported_algorithm; + dns_rdata_t rdata; + bool resume; + uint32_t *nvalidations; + uint32_t *nfails; + isc_counter_t *qc; }; /*% @@ -170,7 +171,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_message_t *message, unsigned int options, isc_loop_t *loop, isc_job_cb cb, void *arg, uint32_t *nvalidations, uint32_t *nfails, - dns_validator_t **validatorp); + isc_counter_t *qc, dns_validator_t **validatorp); /*%< * Start a DNSSEC validation. * diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index ddba1e7401..02932e8a94 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -185,6 +185,7 @@ struct dns_view { unsigned int udpsize; uint32_t maxrrperset; uint32_t maxtypepername; + uint8_t max_restarts; /* * Configurable data for server use only, @@ -1327,4 +1328,16 @@ dns_view_getadb(dns_view_t *view, dns_adb_t **adbp); *\li 'adbp' is non-NULL and '*adbp' is NULL. */ +void +dns_view_setmaxrestarts(dns_view_t *view, uint8_t max_restarts); +/*%< + * Set the number of permissible chained queries before we give up, + * to prevent CNAME loops. This defaults to 11. + * + * Requires: + * + *\li 'view' is valid; + *\li 'max_restarts' is greater than 0. + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index a2fd672d79..78e0634aab 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -215,7 +215,7 @@ /* The default maximum number of iterative queries to allow before giving up. */ #ifndef DEFAULT_MAX_QUERIES -#define DEFAULT_MAX_QUERIES 100 +#define DEFAULT_MAX_QUERIES 50 #endif /* ifndef DEFAULT_MAX_QUERIES */ /* @@ -991,7 +991,7 @@ valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, result = dns_validator_create( fctx->res->view, name, type, rdataset, sigrdataset, message, valoptions, fctx->loop, validated, valarg, &fctx->nvalidations, - &fctx->nfails, &validator); + &fctx->nfails, fctx->qc, &validator); RUNTIME_CHECK(result == ISC_R_SUCCESS); inc_stats(fctx->res, dns_resstatscounter_val); if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { @@ -4520,16 +4520,6 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, isc_mutex_init(&fctx->lock); - if (qc != NULL) { - isc_counter_attach(qc, &fctx->qc); - } else { - result = isc_counter_create(fctx->mctx, res->maxqueries, - &fctx->qc); - if (result != ISC_R_SUCCESS) { - goto cleanup_fetch; - } - } - /* * Make fctx->info point to a copy of a formatted string * "name/type". FCTXTRACE won't work until this is done. @@ -4542,6 +4532,24 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, FCTXTRACE("create"); + if (qc != NULL) { + isc_counter_attach(qc, &fctx->qc); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(9), + "fctx %p(%s): attached to counter %p (%d)", fctx, + fctx->info, fctx->qc, isc_counter_used(fctx->qc)); + } else { + result = isc_counter_create(fctx->mctx, res->maxqueries, + &fctx->qc); + if (result != ISC_R_SUCCESS) { + goto cleanup_fetch; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(9), + "fctx %p(%s): created counter %p", fctx, + fctx->info, fctx->qc); + } + #if DNS_RESOLVER_TRACE fprintf(stderr, "fetchctx__init:%s:%s:%d:%p:%p->references = 1\n", __func__, __FILE__, __LINE__, fctx, fctx); @@ -7128,7 +7136,7 @@ resume_dslookup(void *arg) { fetchctx_ref(fctx); result = dns_resolver_createfetch( res, fctx->nsname, dns_rdatatype_ns, domain, nsrdataset, - NULL, NULL, 0, fctx->options, 0, NULL, loop, + NULL, NULL, 0, fctx->options, 0, fctx->qc, loop, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); if (result != ISC_R_SUCCESS) { @@ -9556,8 +9564,8 @@ rctx_chaseds(respctx_t *rctx, dns_message_t *message, fetchctx_ref(fctx); result = dns_resolver_createfetch( fctx->res, fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL, - NULL, 0, fctx->options, 0, NULL, fctx->loop, resume_dslookup, - fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); + NULL, 0, fctx->options, 0, fctx->qc, fctx->loop, + resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); if (result != ISC_R_SUCCESS) { if (result == DNS_R_DUPLICATE) { result = DNS_R_SERVFAIL; diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 7c4135b61c..814551d759 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -974,9 +975,10 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA)); validator_logcreate(val, name, type, caller, "validator"); - result = dns_validator_create( - val->view, name, type, rdataset, sig, NULL, vopts, val->loop, - cb, val, val->nvalidations, val->nfails, &val->subvalidator); + result = dns_validator_create(val->view, name, type, rdataset, sig, + NULL, vopts, val->loop, cb, val, + val->nvalidations, val->nfails, val->qc, + &val->subvalidator); if (result == ISC_R_SUCCESS) { dns_validator_attach(val, &val->subvalidator->parent); val->subvalidator->depth = val->depth + 1; @@ -3355,7 +3357,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_message_t *message, unsigned int options, isc_loop_t *loop, isc_job_cb cb, void *arg, uint32_t *nvalidations, uint32_t *nfails, - dns_validator_t **validatorp) { + isc_counter_t *qc, dns_validator_t **validatorp) { isc_result_t result = ISC_R_FAILURE; dns_validator_t *val = NULL; dns_keytable_t *kt = NULL; @@ -3395,6 +3397,10 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_message_attach(message, &val->message); } + if (qc != NULL) { + isc_counter_attach(qc, &val->qc); + } + val->mustbesecure = dns_resolver_getmustbesecure(view->resolver, name); dns_rdataset_init(&val->fdsset); dns_rdataset_init(&val->frdataset); @@ -3470,6 +3476,9 @@ destroy_validator(dns_validator_t *val) { if (val->message != NULL) { dns_message_detach(&val->message); } + if (val->qc != NULL) { + isc_counter_detach(&val->qc); + } dns_view_detach(&val->view); isc_mem_put(mctx, val, sizeof(*val)); } diff --git a/lib/dns/view.c b/lib/dns/view.c index 99d8b61b59..bcac41bb80 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -74,6 +74,12 @@ #define DNS_VIEW_DELONLYHASH 111 +/*% + * Default maximum number of chained queries before we give up + * to prevent CNAME loops. + */ +#define DEFAULT_MAX_RESTARTS 11 + /*% * Default EDNS0 buffer size */ @@ -116,6 +122,7 @@ dns_view_create(isc_mem_t *mctx, dns_dispatchmgr_t *dispatchmgr, .trust_anchor_telemetry = true, .root_key_sentinel = true, .udpsize = DEFAULT_EDNS_BUFSIZE, + .max_restarts = DEFAULT_MAX_RESTARTS, }; isc_refcount_init(&view->references, 1); @@ -2455,3 +2462,11 @@ dns_view_getadb(dns_view_t *view, dns_adb_t **adbp) { } rcu_read_unlock(); } + +void +dns_view_setmaxrestarts(dns_view_t *view, uint8_t max_restarts) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(max_restarts > 0); + + view->max_restarts = max_restarts; +} diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 8dd8efa934..595a0694b7 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -2108,6 +2108,20 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, } } + obj = NULL; + (void)cfg_map_get(options, "max-query-restarts", &obj); + if (obj != NULL) { + uint32_t restarts = cfg_obj_asuint32(obj); + if (restarts == 0 || restarts > 255) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'max-query-restarts' is out of " + "range 1..255)"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_RANGE; + } + } + } + if (actx != NULL) { cfg_aclconfctx_detach(&actx); } diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index a69c559f38..0ad4eb6755 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2153,6 +2153,7 @@ static cfg_clausedef_t view_clauses[] = { { "max-ncache-ttl", &cfg_type_duration, 0 }, { "max-recursion-depth", &cfg_type_uint32, 0 }, { "max-recursion-queries", &cfg_type_uint32, 0 }, + { "max-query-restarts", &cfg_type_uint32, 0 }, { "max-stale-ttl", &cfg_type_duration, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, { "max-validations-per-fetch", &cfg_type_uint32, diff --git a/lib/ns/include/ns/server.h b/lib/ns/include/ns/server.h index fc01ee2c62..ec6636a0fd 100644 --- a/lib/ns/include/ns/server.h +++ b/lib/ns/include/ns/server.h @@ -103,6 +103,7 @@ struct ns_server { uint16_t transfer_tcp_message_size; bool interface_auto; dns_tkeyctx_t *tkeyctx; + uint8_t max_restarts; /*% Server id for NSID */ char *server_id; diff --git a/lib/ns/query.c b/lib/ns/query.c index 0c36f7ae2b..01b24dcdfa 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -86,12 +86,6 @@ #define dns64_bis_return_excluded_addresses 1 #endif /* if 0 */ -/*% - * Maximum number of chained queries before we give up - * to prevent CNAME loops. - */ -#define MAX_RESTARTS 16 - #define QUERY_ERROR(qctx, r) \ do { \ (qctx)->result = r; \ @@ -2046,10 +2040,10 @@ addname: /* * In some cases, a record that has been added as additional * data may *also* trigger the addition of additional data. - * This cannot go more than MAX_RESTARTS levels deep. + * This cannot go more than 'max-restarts' levels deep. */ if (trdataset != NULL && dns_rdatatype_followadditional(type)) { - if (client->additionaldepth++ < MAX_RESTARTS) { + if (client->additionaldepth++ < client->view->max_restarts) { eresult = dns_rdataset_additionaldata( trdataset, fname, query_additional_cb, qctx); } @@ -11580,7 +11574,9 @@ ns_query_done(query_ctx_t *qctx) { * Do we need to restart the query (e.g. for CNAME chaining)? */ if (qctx->want_restart) { - if (qctx->client->query.restarts < MAX_RESTARTS) { + if (qctx->client->query.restarts < + qctx->client->view->max_restarts) + { query_ctx_t *saved_qctx = NULL; qctx->client->query.restarts++; saved_qctx = isc_mem_get(qctx->client->manager->mctx, diff --git a/lib/ns/server.c b/lib/ns/server.c index ea4a588c18..94ee48b76c 100644 --- a/lib/ns/server.c +++ b/lib/ns/server.c @@ -38,7 +38,7 @@ void ns_server_create(isc_mem_t *mctx, ns_matchview_t matchingview, ns_server_t **sctxp) { - ns_server_t *sctx; + ns_server_t *sctx = NULL; REQUIRE(sctxp != NULL && *sctxp == NULL);