diff --git a/bin/delv/delv.c b/bin/delv/delv.c index 063ae1e7a9..3cf4bd2e1a 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -88,7 +88,8 @@ #define MAXNAME (DNS_NAME_MAXTEXT + 1) -#define MAX_QUERIES 32 +#define MAX_QUERIES 50 +#define MAX_TOTAL 200 #define MAX_RESTARTS 11 /* Variables used internally by delv. */ @@ -134,6 +135,7 @@ static bool showcomments = true, showdnssec = true, showtrust = true, yaml = false, fulltrace = false; static uint32_t maxqueries = MAX_QUERIES; +static uint32_t maxtotal = MAX_TOTAL; static uint32_t restarts = MAX_RESTARTS; static bool resolve_trace = false, validator_trace = false, @@ -1197,22 +1199,46 @@ plus_option(char *option) { break; case 'm': switch (cmd[1]) { - case 'a': /* maxqueries */ - FULLCHECK("maxqueries"); - if (value == NULL) { - goto need_value; - } - if (!state) { + case 'a': + switch (cmd[3]) { + case 'q': /* 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': /* maxtotalqueries */ + FULLCHECK("maxtotalqueries"); + if (value == NULL) { + goto need_value; + } + if (!state) { + goto invalid_option; + } + result = parse_uint(&maxtotal, value, UINT_MAX, + "maxtotalqueries"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse maxtotalqueries"); + } + if (maxtotal == 0) { + fatal("maxtotalqueries must be " + "nonzero"); + } + break; + default: 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"); @@ -1935,6 +1961,7 @@ run_resolve(void *arg) { CHECK(dns_client_create(mctx, loopmgr, netmgr, 0, tlsctx_client_cache, &client, srcaddr4, srcaddr6)); dns_client_setmaxrestarts(client, restarts); + dns_client_setmaxqueries(client, maxtotal); /* Set the nameserver */ if (server != NULL) { @@ -2200,6 +2227,7 @@ run_server(void *arg) { dns_cache_detach(&cache); dns_view_setdstport(view, destport); dns_view_setmaxrestarts(view, restarts); + dns_view_setmaxqueries(view, maxtotal); CHECK(dns_rootns_create(mctx, dns_rdataclass_in, hintfile, &roothints)); dns_view_sethints(view, roothints); diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 74239c9bc1..dffb37b3ad 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -347,7 +347,12 @@ assign values to options like the timeout interval. They have the form .. option:: +maxqueries This option specifies the maximum number of queries to send to resolve - a name before giving up. The default is 32. + a name before giving up. The default is 50. + +.. option:: +maxtotalqueries + + This option specifies the maximum number of queries to send to resolve + a client request before giving up. The default is 200. .. option:: +trust, +notrust diff --git a/bin/named/config.c b/bin/named/config.c index 04a4306f7b..ea0a861413 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -172,7 +172,8 @@ options {\n\ max-clients-per-query 100;\n\ max-ncache-ttl 10800; /* 3 hours */\n\ max-recursion-depth 7;\n\ - max-recursion-queries 32;\n\ + max-recursion-queries 50;\n\ + max-query-count 200;\n\ max-query-restarts 11;\n\ max-stale-ttl 86400; /* 1 day */\n\ message-compression yes;\n\ diff --git a/bin/named/server.c b/bin/named/server.c index 0f507b987f..770f94a201 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -5600,6 +5600,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, INSIST(result == ISC_R_SUCCESS); dns_view_setmaxrestarts(view, cfg_obj_asuint32(obj)); + obj = NULL; + result = named_config_get(maps, "max-query-count", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_view_setmaxqueries(view, cfg_obj_asuint32(obj)); + obj = NULL; result = named_config_get(maps, "max-validations-per-fetch", &obj); if (result == ISC_R_SUCCESS) { @@ -7361,7 +7366,7 @@ tat_send(void *arg) { if (result == ISC_R_SUCCESS) { result = dns_resolver_createfetch( tat->view->resolver, tatname, dns_rdatatype_null, - domain, &nameservers, NULL, NULL, 0, 0, 0, NULL, + domain, &nameservers, NULL, NULL, 0, 0, 0, NULL, NULL, tat->loop, tat_done, tat, &tat->rdataset, &tat->sigrdataset, &tat->fetch); } diff --git a/bin/tests/system/Makefile.am b/bin/tests/system/Makefile.am index 54fd7ed5fa..ed44ecdc42 100644 --- a/bin/tests/system/Makefile.am +++ b/bin/tests/system/Makefile.am @@ -86,6 +86,7 @@ TESTS = \ autosign \ builtin \ cacheclean \ + camp \ case \ catz \ cds \ diff --git a/bin/tests/system/camp/ns1/named.conf.in b/bin/tests/system/camp/ns1/named.conf.in new file mode 100644 index 0000000000..208a651b0c --- /dev/null +++ b/bin/tests/system/camp/ns1/named.conf.in @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS1 + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +zone "." { + type primary; + file "root.db"; +}; diff --git a/bin/tests/system/camp/ns1/root.db b/bin/tests/system/camp/ns1/root.db new file mode 100644 index 0000000000..64c8ac5910 --- /dev/null +++ b/bin/tests/system/camp/ns1/root.db @@ -0,0 +1,51 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +. IN SOA gson.nominum.com. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +tld0. NS ns.tld0. +ns.tld0. A 10.53.0.2 + +tld1. NS ns.tld1. +ns.tld1. A 10.53.0.2 + +tld2. NS ns.tld2. +ns.tld2. A 10.53.0.2 + +tld3. NS ns.tld3. +ns.tld3. A 10.53.0.2 + +tld4. NS ns.tld4. +ns.tld4. A 10.53.0.2 + +tld5. NS ns.tld5. +ns.tld5. A 10.53.0.2 + +tld6. NS ns.tld6. +ns.tld6. A 10.53.0.2 + +tld7. NS ns.tld7. +ns.tld7. A 10.53.0.2 + +tld8. NS ns.tld8. +ns.tld8. A 10.53.0.2 + +tld9. NS ns.tld9. +ns.tld9. A 10.53.0.2 diff --git a/bin/tests/system/camp/ns2/named.conf.in b/bin/tests/system/camp/ns2/named.conf.in new file mode 100644 index 0000000000..52b20438a9 --- /dev/null +++ b/bin/tests/system/camp/ns2/named.conf.in @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +zone "tld0" { + type primary; + file "tld0.db"; +}; diff --git a/bin/tests/system/camp/ns2/setup.sh b/bin/tests/system/camp/ns2/setup.sh new file mode 100644 index 0000000000..d75a190e0d --- /dev/null +++ b/bin/tests/system/camp/ns2/setup.sh @@ -0,0 +1,57 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +echo_i "ns2/setup.sh" + +cp template.db.in tld0.db +echo "final.tld0. IN NS ns.final.tld0." >>tld0.db +echo "ns.final.tld0. IN A 10.53.0.3" >>tld0.db + +DEPTH=5 + +tld=1 +while [ $tld -le $DEPTH ]; do + + cat >>"named.conf" <>tld${tld}.db + echo "ns.label${label}.tld${tld}. IN A 10.53.0.3" >>tld${tld}.db + echo "" >>tld${tld}.db + + label=$((label + 1)) + done + + tld=$((tld + 1)) +done + +goto=1 +tld=1 +while [ $goto -le $DEPTH ]; do + echo "goto${goto}.tld${tld}. IN NS ns.goto${goto}.tld${tld}." >>tld${tld}.db + echo "ns.goto${goto}.tld${tld}. IN A 10.53.0.3" >>tld${tld}.db + echo "" >>tld${tld}.db + + goto=$((goto + 1)) +done diff --git a/bin/tests/system/camp/ns2/template.db.in b/bin/tests/system/camp/ns2/template.db.in new file mode 100644 index 0000000000..278478e86c --- /dev/null +++ b/bin/tests/system/camp/ns2/template.db.in @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + NS ns +ns A 10.53.0.2 + +; camp attack diff --git a/bin/tests/system/camp/ns3/named.conf.in b/bin/tests/system/camp/ns3/named.conf.in new file mode 100644 index 0000000000..958119bcae --- /dev/null +++ b/bin/tests/system/camp/ns3/named.conf.in @@ -0,0 +1,42 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS3 + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + + max-query-count 150; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "final.tld0" { + type primary; + file "final.tld0.db"; +}; diff --git a/bin/tests/system/camp/ns3/setup.sh b/bin/tests/system/camp/ns3/setup.sh new file mode 100644 index 0000000000..79d1f6cb9c --- /dev/null +++ b/bin/tests/system/camp/ns3/setup.sh @@ -0,0 +1,79 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +echo_i "ns3/setup.sh" + +cp template.db.in final.tld0.db +echo "q.final.tld0. IN A 1.2.3.4" >>final.tld0.db + +DEPTH=5 + +tld=1 +while [ $tld -le $DEPTH ]; do + nexttld=$((tld + 1)) + + label=1 + while [ $label -le $DEPTH ]; do + nextlabel=$((label + 1)) + + cat >>"named.conf" <>label${label}.tld${tld}.db + elif [ $tld -eq $DEPTH ]; then + nextlabel=$((label + 1)) + echo "q.label${label}.tld${tld}. IN CNAME q.label${nextlabel}.tld1." >>label${label}.tld${tld}.db + else + echo "q.label${label}.tld${tld}. IN CNAME q.label${label}.tld${nexttld}." >>label${label}.tld${tld}.db + fi + + label=$nextlabel + done + + echo "" >>label${label}.tld${tld}.db + tld=$nexttld +done + +goto=1 +tld=1 +while [ $goto -le $DEPTH ]; do + nextgoto=$((goto + 1)) + + cat >>"named.conf" <>goto${goto}.tld${tld}.db + else + echo "q.goto${goto}.tld${tld}. IN CNAME q.goto${nextgoto}.tld${tld}." >>goto${goto}.tld${tld}.db + fi + + echo "" >>label${label}.tld${tld}.db + goto=$nextgoto +done diff --git a/bin/tests/system/camp/ns3/template.db.in b/bin/tests/system/camp/ns3/template.db.in new file mode 100644 index 0000000000..dc6bd9b045 --- /dev/null +++ b/bin/tests/system/camp/ns3/template.db.in @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns + NS ns3 +ns A 10.53.0.3 +ns3 A 10.53.0.3 + +; camp attack diff --git a/bin/tests/system/camp/ns9/hints.db b/bin/tests/system/camp/ns9/hints.db new file mode 100644 index 0000000000..691e813076 --- /dev/null +++ b/bin/tests/system/camp/ns9/hints.db @@ -0,0 +1,13 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +. 60 IN NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 diff --git a/bin/tests/system/camp/ns9/named.conf.in b/bin/tests/system/camp/ns9/named.conf.in new file mode 100644 index 0000000000..89045ad092 --- /dev/null +++ b/bin/tests/system/camp/ns9/named.conf.in @@ -0,0 +1,41 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS9 + +options { + query-source address 10.53.0.9; + notify-source 10.53.0.9; + transfer-source 10.53.0.9; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.9; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + + max-recursion-queries 50; + max-query-restarts 50; + max-query-count 100; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.9 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { type hint; file "hints.db"; }; diff --git a/bin/tests/system/camp/setup.sh b/bin/tests/system/camp/setup.sh new file mode 100644 index 0000000000..8d17ffc715 --- /dev/null +++ b/bin/tests/system/camp/setup.sh @@ -0,0 +1,32 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../conf.sh + +set -e + +copy_setports ns1/named.conf.in ns1/named.conf +copy_setports ns2/named.conf.in ns2/named.conf +copy_setports ns3/named.conf.in ns3/named.conf +copy_setports ns9/named.conf.in ns9/named.conf + +( + cd ns3 + $SHELL setup.sh +) + +( + cd ns2 + $SHELL setup.sh +) diff --git a/bin/tests/system/camp/tests.sh b/bin/tests/system/camp/tests.sh new file mode 100755 index 0000000000..3f831f45ea --- /dev/null +++ b/bin/tests/system/camp/tests.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +set -e + +# shellcheck source=../conf.sh +. ../conf.sh + +dig_with_opts() { + "${DIG}" -p "${PORT}" "${@}" +} + +status=0 +n=0 + +n=$((n + 1)) +echo_i "checking max-query-count is in effect ($n)" +ret=0 +dig_with_opts q.label1.tld1. @10.53.0.9 a >dig.out.ns9.test${n} || ret=1 +grep "status: SERVFAIL" dig.out.ns9.test${n} >/dev/null || ret=1 +grep "exceeded global max queries resolving" ns9/named.run >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/camp/tests_sh_camp.py b/bin/tests/system/camp/tests_sh_camp.py new file mode 100644 index 0000000000..177a0f2ae7 --- /dev/null +++ b/bin/tests/system/camp/tests_sh_camp.py @@ -0,0 +1,25 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import pytest + +pytestmark = pytest.mark.extra_artifacts( + [ + "dig.out*", + "ans*/ans.run", + "ns*/*.jnl", + "ns*/*tld*.db", + ] +) + + +def test_camp(run_tests_sh): + run_tests_sh() diff --git a/bin/tests/system/checkconf/good.conf.in b/bin/tests/system/checkconf/good.conf.in index f7e8cc8415..1586710dac 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-count 100; max-query-restarts 10; nta-lifetime 604800; nta-recheck 604800; diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 95981e859c..64894dde5f 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -4600,6 +4600,15 @@ Tuning format is more human-readable, and is thus suitable when a zone is to be edited by hand. The default is ``relative``. +.. namedconf:statement:: max-query-count + :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 + 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. The default is ``200``. + .. namedconf:statement:: max-recursion-depth :tags: server :short: Sets the maximum number of levels of recursion permitted at any one time while servicing a recursive query. @@ -4620,7 +4629,7 @@ Tuning 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. + counted separately.) The default is 50. .. namedconf:statement:: max-query-restarts :tags: server, query diff --git a/doc/misc/options b/doc/misc/options index 210d56c468..71742eef4c 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -183,6 +183,7 @@ options { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; + max-query-count ; max-query-restarts ; max-records ; max-records-per-type ; @@ -474,6 +475,7 @@ view [ ] { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; + max-query-count ; max-query-restarts ; max-records ; max-records-per-type ; diff --git a/lib/dns/adb.c b/lib/dns/adb.c index 5760de8267..e112c14cfe 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -335,7 +335,7 @@ static isc_result_t dbfind_name(dns_adbname_t *, isc_stdtime_t, dns_rdatatype_t); static isc_result_t fetch_name(dns_adbname_t *, bool, unsigned int, isc_counter_t *qc, - dns_rdatatype_t); + isc_counter_t *gqc, dns_rdatatype_t); static void destroy(dns_adb_t *); static void @@ -1926,7 +1926,7 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg, const dns_name_t *name, const dns_name_t *qname, dns_rdatatype_t qtype ISC_ATTR_UNUSED, unsigned int options, isc_stdtime_t now, dns_name_t *target, in_port_t port, - unsigned int depth, isc_counter_t *qc, + unsigned int depth, isc_counter_t *qc, isc_counter_t *gqc, dns_adbfind_t **findp) { isc_result_t result = ISC_R_UNEXPECTED; dns_adbfind_t *find = NULL; @@ -2136,7 +2136,7 @@ fetch: * Start V4. */ if (WANT_INET(wanted_fetches) && - fetch_name(adbname, start_at_zone, depth, qc, + fetch_name(adbname, start_at_zone, depth, qc, gqc, dns_rdatatype_a) == ISC_R_SUCCESS) { DP(DEF_LEVEL, @@ -2149,7 +2149,7 @@ fetch: * Start V6. */ if (WANT_INET6(wanted_fetches) && - fetch_name(adbname, start_at_zone, depth, qc, + fetch_name(adbname, start_at_zone, depth, qc, gqc, dns_rdatatype_aaaa) == ISC_R_SUCCESS) { DP(DEF_LEVEL, @@ -2955,7 +2955,7 @@ out: static isc_result_t fetch_name(dns_adbname_t *adbname, bool start_at_zone, unsigned int depth, - isc_counter_t *qc, dns_rdatatype_t type) { + isc_counter_t *qc, isc_counter_t *gqc, dns_rdatatype_t type) { isc_result_t result; dns_adbfetch_t *fetch = NULL; dns_adb_t *adb = NULL; @@ -3011,7 +3011,7 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, unsigned int depth, */ result = dns_resolver_createfetch( adb->res, adbname->name, type, name, nameservers, NULL, NULL, 0, - options, depth, qc, isc_loop(), fetch_callback, adbname, + options, depth, qc, gqc, isc_loop(), fetch_callback, adbname, &fetch->rdataset, NULL, &fetch->fetch); if (result != ISC_R_SUCCESS) { DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s", diff --git a/lib/dns/client.c b/lib/dns/client.c index 96043e0266..fa4ba6ee84 100644 --- a/lib/dns/client.c +++ b/lib/dns/client.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -81,6 +82,7 @@ struct dns_client { unsigned int find_timeout; unsigned int find_udpretries; uint8_t max_restarts; + uint8_t max_queries; isc_refcount_t references; @@ -91,6 +93,7 @@ struct dns_client { #define DEF_FIND_TIMEOUT 5 #define DEF_FIND_UDPRETRIES 3 #define DEF_MAX_RESTARTS 11 +#define DEF_MAX_QUERIES 200 /*% * Internal state for a single name resolution procedure @@ -111,6 +114,7 @@ typedef struct resctx { dns_fetch_t *fetch; dns_namelist_t namelist; isc_result_t result; + isc_counter_t *qc; dns_clientresume_t *rev; dns_rdataset_t *rdataset; dns_rdataset_t *sigrdataset; @@ -252,6 +256,7 @@ dns_client_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, isc_nm_t *nm, .loop = isc_loop_get(loopmgr, 0), .nm = nm, .max_restarts = DEF_MAX_RESTARTS, + .max_queries = DEF_MAX_QUERIES, }; result = dns_dispatchmgr_create(mctx, loopmgr, nm, @@ -395,6 +400,14 @@ dns_client_setmaxrestarts(dns_client_t *client, uint8_t max_restarts) { client->max_restarts = max_restarts; } +void +dns_client_setmaxqueries(dns_client_t *client, uint8_t max_queries) { + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(max_queries > 0); + + client->max_queries = max_queries; +} + static isc_result_t getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { dns_rdataset_t *rdataset; @@ -456,7 +469,7 @@ start_fetch(resctx_t *rctx) { result = dns_resolver_createfetch( rctx->view->resolver, dns_fixedname_name(&rctx->name), - rctx->type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL, + rctx->type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL, rctx->qc, rctx->client->loop, fetch_done, rctx, rctx->rdataset, rctx->sigrdataset, &rctx->fetch); @@ -935,6 +948,11 @@ startresolve(dns_client_t *client, const dns_name_t *name, rctx->magic = RCTX_MAGIC; isc_refcount_increment(&client->references); + result = isc_counter_create(mctx, client->max_queries, &rctx->qc); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(client->resctxs, rctx, link); *transp = (dns_clientrestrans_t *)rctx; @@ -949,6 +967,9 @@ cleanup: if (sigrdataset != NULL) { putrdataset(client->mctx, &sigrdataset); } + if (rctx->qc != NULL) { + isc_counter_detach(&rctx->qc); + } isc_mem_put(mctx, rctx, sizeof(*rctx)); isc_mem_put(mctx, rev, sizeof(*rev)); @@ -1042,6 +1063,9 @@ destroyrestrans(dns_clientrestrans_t **transp) { rctx->magic = 0; + if (rctx->qc != NULL) { + isc_counter_detach(&rctx->qc); + } isc_mem_put(mctx, rctx, sizeof(*rctx)); } diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h index 7288cdd527..585f0f6c9b 100644 --- a/lib/dns/include/dns/adb.h +++ b/lib/dns/include/dns/adb.h @@ -281,7 +281,8 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg, const dns_name_t *name, const dns_name_t *qname, dns_rdatatype_t qtype, unsigned int options, isc_stdtime_t now, dns_name_t *target, in_port_t port, - unsigned int depth, isc_counter_t *qc, dns_adbfind_t **find); + unsigned int depth, isc_counter_t *qc, isc_counter_t *gqc, + dns_adbfind_t **find); /*%< * Main interface for clients. The adb will look up the name given in * "name" and will build up a list of found addresses, and perhaps start diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h index c065c0c705..75cbf89250 100644 --- a/lib/dns/include/dns/client.h +++ b/lib/dns/include/dns/client.h @@ -187,6 +187,19 @@ dns_client_setmaxrestarts(dns_client_t *client, uint8_t max_restarts); *\li 'max_restarts' is greater than 0. */ +void +dns_client_setmaxqueries(dns_client_t *client, uint8_t max_queries); +/*%< + * Set the number of permissible outgoing queries before we give up, + * This defaults to 200. + * + * Requires: + * + *\li 'client' is a valid client. + + *\li 'max_queries' 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/resolver.h b/lib/dns/include/dns/resolver.h index 1ed174e5e1..a9aa1ffa97 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -272,9 +272,10 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, dns_forwarders_t *forwarders, const isc_sockaddr_t *client, dns_messageid_t id, unsigned int options, unsigned int depth, - isc_counter_t *qc, isc_loop_t *loop, isc_job_cb cb, - void *arg, dns_rdataset_t *rdataset, - dns_rdataset_t *sigrdataset, dns_fetch_t **fetchp); + isc_counter_t *qc, isc_counter_t *gqc, + isc_loop_t *loop, isc_job_cb cb, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp); /*%< * Recurse to answer a question. * diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index 249687d756..09dc3a13a3 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -154,6 +154,7 @@ struct dns_validator { uint32_t *nvalidations; uint32_t *nfails; isc_counter_t *qc; + isc_counter_t *gqc; }; /*% @@ -172,7 +173,8 @@ 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, - isc_counter_t *qc, dns_validator_t **validatorp); + isc_counter_t *qc, isc_counter_t *gqc, + 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 fab923f15e..b840df8874 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; + uint16_t max_queries; uint8_t max_restarts; /* @@ -1341,4 +1342,16 @@ dns_view_setmaxrestarts(dns_view_t *view, uint8_t max_restarts); *\li 'max_restarts' is greater than 0. */ +void +dns_view_setmaxqueries(dns_view_t *view, uint16_t max_queries); +/*% + * Set the number of permissible outgoing queries before we give up. + * This defaults to 200. + * + * Requires: + * + *\li 'view' is valid; + *\li 'max_queries' is greater than 0. + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/nta.c b/lib/dns/nta.c index 45101c0c44..8b81d1bbda 100644 --- a/lib/dns/nta.c +++ b/lib/dns/nta.c @@ -241,8 +241,9 @@ checkbogus(void *arg) { dns__nta_ref(nta); /* for dns_resolver_createfetch */ result = dns_resolver_createfetch( resolver, &nta->name, dns_rdatatype_nsec, NULL, NULL, NULL, - NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, nta->loop, fetch_done, - nta, &nta->rdataset, &nta->sigrdataset, &nta->fetch); + NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, NULL, nta->loop, + fetch_done, nta, &nta->rdataset, &nta->sigrdataset, + &nta->fetch); if (result != ISC_R_SUCCESS) { dns__nta_detach(&nta); /* for dns_resolver_createfetch() */ } diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 69df57fd06..614cb54428 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -395,6 +395,7 @@ struct fetchctx { bool ns_ttl_ok; uint32_t ns_ttl; isc_counter_t *qc; + isc_counter_t *gqc; bool minimized; unsigned int qmin_labels; isc_result_t qmin_warning; @@ -573,6 +574,7 @@ struct dns_resolver { unsigned int query_timeout; unsigned int maxdepth; unsigned int maxqueries; + unsigned int maxquerycount; isc_result_t quotaresp[2]; isc_stats_t *stats; dns_stats_t *querystats; @@ -713,7 +715,7 @@ get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, dns_rdatatype_t type, const dns_name_t *domain, dns_rdataset_t *nameservers, const isc_sockaddr_t *client, unsigned int options, unsigned int depth, isc_counter_t *qc, - fetchctx_t **fctxp, bool *new_fctx); + isc_counter_t *gqc, fetchctx_t **fctxp, bool *new_fctx); static void release_fctx(fetchctx_t *fctx); @@ -987,7 +989,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, fctx->qc, &validator); + &fctx->nfails, fctx->qc, fctx->gqc, &validator); RUNTIME_CHECK(result == ISC_R_SUCCESS); inc_stats(fctx->res, dns_resstatscounter_val); if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { @@ -3285,7 +3287,7 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, result = dns_adb_createfind(fctx->adb, fctx->loop, fctx_finddone, fctx, name, fctx->name, fctx->type, options, now, NULL, res->view->dstport, fctx->depth + 1, - fctx->qc, &find); + fctx->qc, fctx->gqc, &find); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), @@ -3985,6 +3987,9 @@ fctx_try(fetchctx_t *fctx, bool retrying) { dns_resolver_t *res = NULL; FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc)); + if (fctx->gqc != NULL) { + FCTXTRACE5("try", "fctx->gqc=", isc_counter_used(fctx->gqc)); + } REQUIRE(!ADDRWAIT(fctx)); REQUIRE(fctx->tid == isc_tid()); @@ -3992,13 +3997,27 @@ fctx_try(fetchctx_t *fctx, bool retrying) { res = fctx->res; /* We've already exceeded maximum query count */ - if (isc_counter_used(fctx->qc) > res->maxqueries) { + if (isc_counter_used(fctx->qc) > isc_counter_getlimit(fctx->qc)) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "exceeded max queries resolving '%s' " + "(max-recursion-queries, querycount=%u, maxqueries=%u)", + fctx->info, isc_counter_used(fctx->qc), + isc_counter_getlimit(fctx->qc)); + result = DNS_R_SERVFAIL; + goto done; + } + + if (fctx->gqc != NULL && + isc_counter_used(fctx->gqc) > isc_counter_getlimit(fctx->gqc)) + { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), - "exceeded max queries resolving '%s' " - "(querycount=%u, maxqueries=%u)", - fctx->info, isc_counter_used(fctx->qc), - res->maxqueries); + "exceeded global max queries resolving '%s' " + "(max-query-count, querycount=%u, maxqueries=%u)", + fctx->info, isc_counter_used(fctx->gqc), + isc_counter_getlimit(fctx->gqc)); result = DNS_R_SERVFAIL; goto done; } @@ -4096,8 +4115,8 @@ fctx_try(fetchctx_t *fctx, bool retrying) { result = dns_resolver_createfetch( fctx->res, fctx->qminname, fctx->qmintype, fctx->domain, &fctx->nameservers, NULL, NULL, 0, options, 0, fctx->qc, - fctx->loop, resume_qmin, fctx, &fctx->qminrrset, NULL, - &fctx->qminfetch); + fctx->gqc, fctx->loop, resume_qmin, fctx, + &fctx->qminrrset, NULL, &fctx->qminfetch); if (result != ISC_R_SUCCESS) { fetchctx_unref(fctx); goto done; @@ -4109,11 +4128,24 @@ fctx_try(fetchctx_t *fctx, bool retrying) { if (result != ISC_R_SUCCESS) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), - "exceeded max queries resolving '%s'", - fctx->info); + "exceeded max queries resolving '%s' " + "(max-recursion-queries, querycount=%u)", + fctx->info, isc_counter_used(fctx->qc)); goto done; } + if (fctx->gqc != NULL) { + result = isc_counter_increment(fctx->gqc); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "exceeded global max queries resolving " + "'%s' (max-query-count, querycount=%u)", + fctx->info, isc_counter_used(fctx->gqc)); + goto done; + } + } + result = fctx_query(fctx, addrinfo, fctx->options); if (result != ISC_R_SUCCESS) { goto done; @@ -4345,6 +4377,9 @@ fctx_destroy(fetchctx_t *fctx) { } isc_counter_detach(&fctx->qc); + if (fctx->gqc != NULL) { + isc_counter_detach(&fctx->gqc); + } fcount_decr(fctx); dns_message_detach(&fctx->qmessage); if (dns_rdataset_isassociated(&fctx->nameservers)) { @@ -4502,7 +4537,7 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, dns_rdatatype_t type, const dns_name_t *domain, dns_rdataset_t *nameservers, const isc_sockaddr_t *client, unsigned int options, unsigned int depth, isc_counter_t *qc, - fetchctx_t **fctxp) { + isc_counter_t *gqc, fetchctx_t **fctxp) { fetchctx_t *fctx = NULL; isc_result_t result; isc_result_t iresult; @@ -4570,6 +4605,15 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, fctx->info, fctx->qc); } + if (gqc != NULL) { + isc_counter_attach(gqc, &fctx->gqc); + 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->gqc, + isc_counter_used(fctx->gqc)); + } + #if DNS_RESOLVER_TRACE fprintf(stderr, "fetchctx__init:%s:%s:%d:%p:%p->references = 1\n", __func__, __FILE__, __LINE__, fctx, fctx); @@ -4779,6 +4823,9 @@ cleanup_nameservers: } isc_mem_free(fctx->mctx, fctx->info); isc_counter_detach(&fctx->qc); + if (fctx->gqc != NULL) { + isc_counter_detach(&fctx->gqc); + } cleanup_fetch: dns_resolver_detach(&fctx->res); @@ -7146,8 +7193,8 @@ 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, fctx->qc, loop, - resume_dslookup, fctx, &fctx->nsrrset, NULL, + NULL, NULL, 0, fctx->options, 0, fctx->qc, fctx->gqc, + loop, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); if (result != ISC_R_SUCCESS) { fetchctx_unref(fctx); @@ -9574,7 +9621,7 @@ 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, fctx->qc, fctx->loop, + NULL, 0, fctx->options, 0, fctx->qc, fctx->gqc, fctx->loop, resume_dslookup, fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); if (result != ISC_R_SUCCESS) { if (result == DNS_R_DUPLICATE) { @@ -10149,8 +10196,9 @@ dns_resolver_prime(dns_resolver_t *res) { LOCK(&res->primelock); result = dns_resolver_createfetch( res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL, - NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL, isc_loop(), - prime_done, res, rdataset, NULL, &res->primefetch); + NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL, NULL, + isc_loop(), prime_done, res, rdataset, NULL, + &res->primefetch); UNLOCK(&res->primelock); if (result != ISC_R_SUCCESS) { @@ -10349,7 +10397,7 @@ get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, dns_rdatatype_t type, const dns_name_t *domain, dns_rdataset_t *nameservers, const isc_sockaddr_t *client, unsigned int options, unsigned int depth, isc_counter_t *qc, - fetchctx_t **fctxp, bool *new_fctx) { + isc_counter_t *gqc, fetchctx_t **fctxp, bool *new_fctx) { isc_result_t result; fetchctx_t key = { .name = UNCONST(name), @@ -10369,7 +10417,7 @@ again: break; case ISC_R_NOTFOUND: result = fctx_create(res, loop, name, type, domain, nameservers, - client, options, depth, qc, &fctx); + client, options, depth, qc, gqc, &fctx); if (result != ISC_R_SUCCESS) { goto unlock; } @@ -10424,9 +10472,10 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, dns_forwarders_t *forwarders, const isc_sockaddr_t *client, dns_messageid_t id, unsigned int options, unsigned int depth, - isc_counter_t *qc, isc_loop_t *loop, isc_job_cb cb, - void *arg, dns_rdataset_t *rdataset, - dns_rdataset_t *sigrdataset, dns_fetch_t **fetchp) { + isc_counter_t *qc, isc_counter_t *gqc, + isc_loop_t *loop, isc_job_cb cb, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp) { dns_fetch_t *fetch = NULL; fetchctx_t *fctx = NULL; isc_result_t result = ISC_R_SUCCESS; @@ -10477,7 +10526,7 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, result = get_attached_fctx(res, loop, name, type, domain, nameservers, client, options, depth, - qc, &fctx, &new_fctx); + qc, gqc, &fctx, &new_fctx); if (result != ISC_R_SUCCESS) { goto fail; } @@ -10513,7 +10562,7 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, } } else { result = fctx_create(res, loop, name, type, domain, nameservers, - client, options, depth, qc, &fctx); + client, options, depth, qc, gqc, &fctx); if (result != ISC_R_SUCCESS) { goto unlock; } diff --git a/lib/dns/validator.c b/lib/dns/validator.c index ae1a04e432..ab13bf6095 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -938,7 +938,7 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, dns_validator_ref(val); result = dns_resolver_createfetch( val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0, - fopts, 0, NULL, val->loop, callback, val, &val->frdataset, + fopts, 0, NULL, NULL, val->loop, callback, val, &val->frdataset, &val->fsigrdataset, &val->fetch); if (result != ISC_R_SUCCESS) { dns_validator_detach(&val); @@ -976,7 +976,7 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, result = dns_validator_create(val->view, name, type, rdataset, sig, NULL, vopts, val->loop, cb, val, val->nvalidations, val->nfails, val->qc, - &val->subvalidator); + val->gqc, &val->subvalidator); if (result == ISC_R_SUCCESS) { dns_validator_attach(val, &val->subvalidator->parent); val->subvalidator->depth = val->depth + 1; @@ -3392,7 +3392,8 @@ 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, - isc_counter_t *qc, dns_validator_t **validatorp) { + isc_counter_t *qc, isc_counter_t *gqc, + dns_validator_t **validatorp) { isc_result_t result = ISC_R_FAILURE; dns_validator_t *val = NULL; dns_keytable_t *kt = NULL; @@ -3435,6 +3436,9 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, if (qc != NULL) { isc_counter_attach(qc, &val->qc); } + if (gqc != NULL) { + isc_counter_attach(gqc, &val->gqc); + } val->mustbesecure = dns_resolver_getmustbesecure(view->resolver, name); dns_rdataset_init(&val->fdsset); @@ -3525,6 +3529,9 @@ destroy_validator(dns_validator_t *val) { if (val->qc != NULL) { isc_counter_detach(&val->qc); } + if (val->gqc != NULL) { + isc_counter_detach(&val->gqc); + } dns_view_detach(&val->view); isc_loop_detach(&val->loop); isc_mem_put(mctx, val, sizeof(*val)); diff --git a/lib/dns/view.c b/lib/dns/view.c index 19cc14ea10..a2c0446787 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -2471,3 +2471,11 @@ dns_view_setmaxrestarts(dns_view_t *view, uint8_t max_restarts) { view->max_restarts = max_restarts; } + +void +dns_view_setmaxqueries(dns_view_t *view, uint16_t max_queries) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(max_queries > 0); + + view->max_queries = max_queries; +} diff --git a/lib/dns/zone.c b/lib/dns/zone.c index d0cd5bd28d..d63459124a 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -10896,7 +10896,7 @@ do_keyfetch(void *arg) { */ result = dns_resolver_createfetch( resolver, kname, dns_rdatatype_dnskey, NULL, NULL, NULL, NULL, - 0, options, 0, NULL, zone->loop, keyfetch_done, kfetch, + 0, options, 0, NULL, NULL, zone->loop, keyfetch_done, kfetch, &kfetch->dnskeyset, &kfetch->dnskeysigset, &kfetch->fetch); dns_resolver_detach(&resolver); @@ -12416,7 +12416,7 @@ notify_find_address(dns_notify_t *notify) { result = dns_adb_createfind( adb, notify->zone->loop, process_notify_adb_event, notify, ¬ify->ns, dns_rootname, 0, options, 0, NULL, - notify->zone->view->dstport, 0, NULL, ¬ify->find); + notify->zone->view->dstport, 0, NULL, NULL, ¬ify->find); dns_adb_detach(&adb); /* Something failed? */ @@ -21276,7 +21276,7 @@ checkds_find_address(dns_checkds_t *checkds) { result = dns_adb_createfind( adb, checkds->zone->loop, process_checkds_adb_event, checkds, &checkds->ns, dns_rootname, 0, options, 0, NULL, - checkds->zone->view->dstport, 0, NULL, &checkds->find); + checkds->zone->view->dstport, 0, NULL, NULL, &checkds->find); dns_adb_detach(&adb); /* Something failed? */ @@ -21861,8 +21861,9 @@ do_nsfetch(void *arg) { */ result = dns_resolver_createfetch( resolver, &nsfetch->pname, dns_rdatatype_ns, NULL, NULL, NULL, - NULL, 0, options, 0, NULL, zone->loop, nsfetch_done, nsfetch, - &nsfetch->nsrrset, &nsfetch->nssigset, &nsfetch->fetch); + NULL, 0, options, 0, NULL, NULL, zone->loop, nsfetch_done, + nsfetch, &nsfetch->nsrrset, &nsfetch->nssigset, + &nsfetch->fetch); dns_resolver_detach(&resolver); diff --git a/lib/isc/counter.c b/lib/isc/counter.c index 452ca10663..0c70c78d1a 100644 --- a/lib/isc/counter.c +++ b/lib/isc/counter.c @@ -80,6 +80,13 @@ isc_counter_setlimit(isc_counter_t *counter, int limit) { atomic_store(&counter->limit, limit); } +unsigned int +isc_counter_getlimit(isc_counter_t *counter) { + REQUIRE(VALID_COUNTER(counter)); + + return atomic_load_acquire(&counter->limit); +} + void isc_counter_attach(isc_counter_t *source, isc_counter_t **targetp) { REQUIRE(VALID_COUNTER(source)); diff --git a/lib/isc/include/isc/counter.h b/lib/isc/include/isc/counter.h index 820e4a2f65..385b08cd4f 100644 --- a/lib/isc/include/isc/counter.h +++ b/lib/isc/include/isc/counter.h @@ -68,6 +68,12 @@ isc_counter_setlimit(isc_counter_t *counter, int limit); * Set the counter limit. */ +unsigned int +isc_counter_getlimit(isc_counter_t *counter); +/*%< + * Get the counter limit. + */ + void isc_counter_attach(isc_counter_t *source, isc_counter_t **targetp); /*%< diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index e88a177dd1..a9f06e9055 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2220,6 +2220,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-count", &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 }, diff --git a/lib/ns/include/ns/query.h b/lib/ns/include/ns/query.h index 572e5ad94e..b340c59e3a 100644 --- a/lib/ns/include/ns/query.h +++ b/lib/ns/include/ns/query.h @@ -105,6 +105,7 @@ typedef struct ns_query_recparam { struct ns_query { unsigned int attributes; unsigned int restarts; + isc_counter_t *qc; bool timerset; dns_name_t *qname; dns_name_t *origqname; diff --git a/lib/ns/query.c b/lib/ns/query.c index a305a59687..f5ebf6242b 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -865,6 +866,9 @@ query_reset(ns_client_t *client, bool everything) { client->query.rpz_st = NULL; } } + if (client->query.qc != NULL) { + isc_counter_detach(&client->query.qc); + } client->query.origqname = NULL; client->query.dboptions = 0; client->query.fetchoptions = 0; @@ -2804,7 +2808,8 @@ fetch_and_forget(ns_client_t *client, dns_name_t *qname, dns_rdatatype_t qtype, result = dns_resolver_createfetch( client->view->resolver, qname, qtype, NULL, NULL, NULL, peeraddr, client->message->id, options, 0, NULL, - client->manager->loop, cb, client, tmprdataset, NULL, fetchp); + client->query.qc, client->manager->loop, cb, client, + tmprdataset, NULL, fetchp); if (result != ISC_R_SUCCESS) { ns_client_putrdataset(client, &tmprdataset); isc_nmhandle_detach(handlep); @@ -6590,8 +6595,9 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, result = dns_resolver_createfetch( client->view->resolver, qname, qtype, qdomain, nameservers, NULL, peeraddr, client->message->id, client->query.fetchoptions, - 0, NULL, client->manager->loop, fetch_callback, client, - rdataset, sigrdataset, &FETCH_RECTYPE_NORMAL(client)); + 0, NULL, client->query.qc, client->manager->loop, + fetch_callback, client, rdataset, sigrdataset, + &FETCH_RECTYPE_NORMAL(client)); if (result != ISC_R_SUCCESS) { release_recursionquota(client); @@ -12251,5 +12257,16 @@ ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) { message->flags |= DNS_MESSAGEFLAG_AD; } + /* + * Start global outgoing query count. + */ + result = isc_counter_create(client->manager->mctx, + client->view->max_queries, + &client->query.qc); + if (result != ISC_R_SUCCESS) { + query_next(client, result); + return; + } + query_setup(client, qtype); }