diff --git a/CHANGES b/CHANGES index 3331514d33..f425a84cea 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,13 @@ BIND 9 version number, in an effort to tightly couple internal libraries with a specific release. [GL #2387] +5562. [func] Limit the size of IXFR responses so that AXFR will + be used instead if it would be smaller. This is + controlled by the "max-ixfr-ratio" option, which + is a percentage representing the ratio of IXFR size + to the size of the entire zone. This value cannot + exceed 100%, which is the default. [GL #1515] + 5561. [bug] KASP incorrectly set signature validity to the value of the DNSKEY signature validity. This is now fixed. [GL #2383] diff --git a/aclocal.m4 b/aclocal.m4 index fd128f0963..afaa95a961 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.16.2 -*- Autoconf -*- +# generated automatically by aclocal 1.16.3 -*- Autoconf -*- # Copyright (C) 1996-2020 Free Software Foundation, Inc. diff --git a/bin/named/config.c b/bin/named/config.c index 210ac8bde6..9c3e8ca799 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -81,6 +81,7 @@ options {\n\ listen-on-v6 {any;};\n\ # lock-file \"" NAMED_LOCALSTATEDIR "/run/named/named.lock\";\n\ match-mapped-addresses no;\n\ + max-ixfr-ratio 100%;\n\ max-rsa-exponent-size 0; /* no limit */\n\ max-udp-size 1232;\n\ memstatistics-file \"named.memstats\";\n\ diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index b2bb3731fb..32b6aedb7f 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1398,6 +1398,15 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, ixfrdiff); } + obj = NULL; + result = named_config_get(maps, "max-ixfr-ratio", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + if (cfg_obj_isstring(obj)) { + dns_zone_setixfrratio(zone, 0); + } else { + dns_zone_setixfrratio(zone, cfg_obj_aspercentage(obj)); + } + obj = NULL; result = named_config_get(maps, "request-expire", &obj); INSIST(result == ISC_R_SUCCESS); diff --git a/bin/tests/system/checkconf/bad-maxratio1.conf b/bin/tests/system/checkconf/bad-maxratio1.conf new file mode 100644 index 0000000000..f0d06be669 --- /dev/null +++ b/bin/tests/system/checkconf/bad-maxratio1.conf @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone example { + type master; + masterfile-format map; + file "example.db"; + max-ixfr-ratio 0.9; +}; diff --git a/bin/tests/system/checkconf/bad-maxratio2.conf b/bin/tests/system/checkconf/bad-maxratio2.conf new file mode 100644 index 0000000000..902c334423 --- /dev/null +++ b/bin/tests/system/checkconf/bad-maxratio2.conf @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone example { + type master; + masterfile-format map; + file "example.db"; + max-ixfr-ratio 0%; +}; diff --git a/bin/tests/system/checkconf/good-maxratio1.conf b/bin/tests/system/checkconf/good-maxratio1.conf new file mode 100644 index 0000000000..80ff113f21 --- /dev/null +++ b/bin/tests/system/checkconf/good-maxratio1.conf @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone example { + type master; + masterfile-format map; + file "example.db"; + max-ixfr-ratio 50%; +}; diff --git a/bin/tests/system/checkconf/good-maxratio2.conf b/bin/tests/system/checkconf/good-maxratio2.conf new file mode 100644 index 0000000000..e057dfe6e1 --- /dev/null +++ b/bin/tests/system/checkconf/good-maxratio2.conf @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone example { + type master; + masterfile-format map; + file "example.db"; + max-ixfr-ratio unlimited; +}; diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf index 117143ad29..e23fd25060 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -81,6 +81,7 @@ options { "corp"; }; dnssec-policy "test"; + max-ixfr-ratio 90%; transfer-source 0.0.0.0 dscp 63; zone-statistics none; }; @@ -92,11 +93,13 @@ view "first" { type master; file "xxx"; update-policy local; + max-ixfr-ratio 20%; notify-source 10.10.10.10 port 53 dscp 55; }; zone "clone" { type master; file "yyy"; + max-ixfr-ratio unlimited; }; dnssec-validation auto; zone-statistics terse; diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index 61e3b8fa4a..19baf1c19b 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -559,9 +559,16 @@ awk 'BEGIN { ok = 0; } /cut here/ { ok = 1; getline } ok == 1 { print }' good-ka [ -s good-kasp.conf.in ] || ret=1 $CHECKCONF -p good-kasp.conf.in | grep -v '^good-kasp.conf.in:' > good-kasp.conf.out 2>&1 || ret=1 cmp good-kasp.conf.in good-kasp.conf.out || ret=1 - if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` +n=`expr $n + 1` +echo_i "check that max-ixfr-ratio 100% generates a warning ($n)" +ret=0 +$CHECKCONF warn-maxratio1.conf > checkconf.out$n 2>/dev/null || ret=1 +grep "exceeds 100%" < checkconf.out$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; ret=1; fi +status=`expr $status + $ret` + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/checkconf/warn-maxratio1.conf b/bin/tests/system/checkconf/warn-maxratio1.conf new file mode 100644 index 0000000000..519dcc484c --- /dev/null +++ b/bin/tests/system/checkconf/warn-maxratio1.conf @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +zone example { + type master; + masterfile-format map; + file "example.db"; + max-ixfr-ratio 101%; +}; diff --git a/bin/tests/system/ixfr/clean.sh b/bin/tests/system/ixfr/clean.sh index 3de07b777b..e1dd69327b 100644 --- a/bin/tests/system/ixfr/clean.sh +++ b/bin/tests/system/ixfr/clean.sh @@ -11,12 +11,12 @@ rm -f stats.* rm -f ns1/*.db ns1/*.jnl -rm -f ns3/*.jnl ns3/mytest.db ns3/subtest.db +rm -f ns3/*.jnl ns3/mytest*.db ns3/subtest*.db rm -f ns4/*.jnl ns4/*.db rm -f ns5/*.jnl ns5/*.db rm -f */named.memstats rm -f */named.conf -rm -f */named.run +rm -f */named.run */named.run.prev rm -f */ans.run rm -f dig.out.test* dig.out1.test* dig.out2.test* dig.out3.test* rm -f ns3/large.db diff --git a/bin/tests/system/ixfr/ixfr-stats.good b/bin/tests/system/ixfr/ixfr-stats.good index dd62e121d6..3d0d2dde32 100644 --- a/bin/tests/system/ixfr/ixfr-stats.good +++ b/bin/tests/system/ixfr/ixfr-stats.good @@ -1,3 +1,3 @@ messages=1 -records=6 -bytes=219 +records=5 +bytes=204 diff --git a/bin/tests/system/ixfr/ns1/.gitignore b/bin/tests/system/ixfr/ns1/.gitignore deleted file mode 100644 index 58e5c9282d..0000000000 --- a/bin/tests/system/ixfr/ns1/.gitignore +++ /dev/null @@ -1 +0,0 @@ -named.conf diff --git a/bin/tests/system/ixfr/ns1/named.conf.in b/bin/tests/system/ixfr/ns1/named.conf.in new file mode 100644 index 0000000000..854d04dedb --- /dev/null +++ b/bin/tests/system/ixfr/ns1/named.conf.in @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +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; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/ixfr/ns3/mytest0.db b/bin/tests/system/ixfr/ns3/mytest0.db deleted file mode 100644 index 644086fb85..0000000000 --- a/bin/tests/system/ixfr/ns3/mytest0.db +++ /dev/null @@ -1,26 +0,0 @@ -; Copyright (C) Internet Systems Consortium, Inc. ("ISC") -; -; 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 http://mozilla.org/MPL/2.0/. -; -; See the COPYRIGHT file distributed with this work for additional -; information regarding copyright ownership. - -$ORIGIN test. -$TTL 15 -test. 15 IN SOA ns1.test. hostmaster.test. ( - 1 ; serial - 3H ; refresh - 15 ; retry - 1w ; expire - 3h ; minimum - ) - IN NS ns1.test. - IN NS ns2.test. - IN NS ns5.test. -ns1 IN A 10.53.0.3 -ns2 IN A 10.53.0.4 -ns5 IN A 10.53.0.5 -host1 IN A 192.168.10.3 -host2 IN A 192.168.10.4 diff --git a/bin/tests/system/ixfr/ns3/mytest1.db b/bin/tests/system/ixfr/ns3/mytest1.db deleted file mode 100644 index ddc7e05d24..0000000000 --- a/bin/tests/system/ixfr/ns3/mytest1.db +++ /dev/null @@ -1,26 +0,0 @@ -; Copyright (C) Internet Systems Consortium, Inc. ("ISC") -; -; 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 http://mozilla.org/MPL/2.0/. -; -; See the COPYRIGHT file distributed with this work for additional -; information regarding copyright ownership. - -$ORIGIN test. -$TTL 15 -test. 15 IN SOA ns1.test. hostmaster.test. ( - 2 ; serial - 3H ; refresh - 15 ; retry - 1w ; expire - 3h ; minimum - ) - IN NS ns1.test. - IN NS ns2.test. - IN NS ns5.test. -ns1 IN A 10.53.0.3 -ns2 IN A 10.53.0.4 -ns5 IN A 10.53.0.5 -host1 IN A 192.168.10.13 -host2 IN A 192.168.10.4 diff --git a/bin/tests/system/ixfr/ns3/mytest2.db b/bin/tests/system/ixfr/ns3/mytest2.db deleted file mode 100644 index 6ea7cf9466..0000000000 --- a/bin/tests/system/ixfr/ns3/mytest2.db +++ /dev/null @@ -1,26 +0,0 @@ -; Copyright (C) Internet Systems Consortium, Inc. ("ISC") -; -; 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 http://mozilla.org/MPL/2.0/. -; -; See the COPYRIGHT file distributed with this work for additional -; information regarding copyright ownership. - -$ORIGIN test. -$TTL 15 -test. 15 IN SOA ns1.test. hostmaster.test. ( - 3 ; serial - 3H ; refresh - 15 ; retry - 1w ; expire - 3h ; minimum - ) - IN NS ns1.test. - IN NS ns2.test. - IN NS ns5.test. -ns1 IN A 10.53.0.3 -ns2 IN A 10.53.0.4 -ns5 IN A 10.53.0.5 -host1 IN A 192.168.10.13 -host2 IN A 192.168.10.14 diff --git a/bin/tests/system/ixfr/ns3/named.conf.in b/bin/tests/system/ixfr/ns3/named.conf.in index c16ac8541c..96d5ab027a 100644 --- a/bin/tests/system/ixfr/ns3/named.conf.in +++ b/bin/tests/system/ixfr/ns3/named.conf.in @@ -37,6 +37,7 @@ view "primary" { zone "test" IN { type primary; file "mytest.db"; + max-ixfr-ratio 75%; }; zone "sub.test" IN { type primary; diff --git a/bin/tests/system/ixfr/ns3/subtest0.db b/bin/tests/system/ixfr/ns3/subtest0.db deleted file mode 100644 index 7dc1ed79e6..0000000000 --- a/bin/tests/system/ixfr/ns3/subtest0.db +++ /dev/null @@ -1,22 +0,0 @@ -; Copyright (C) Internet Systems Consortium, Inc. ("ISC") -; -; 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 http://mozilla.org/MPL/2.0/. -; -; See the COPYRIGHT file distributed with this work for additional -; information regarding copyright ownership. - -$ORIGIN sub.test. -$TTL 15 -sub.test. 15 IN SOA ns1.test. hostmaster.test. ( - 1 ; serial - 3H ; refresh - 15 ; retry - 1w ; expire - 3h ; minimum - ) - IN NS ns1.test. - IN NS ns2.test. -host3 IN A 192.168.10.23 -host4 IN A 192.168.10.24 diff --git a/bin/tests/system/ixfr/ns3/subtest1.db b/bin/tests/system/ixfr/ns3/subtest1.db deleted file mode 100644 index 5af027a4c7..0000000000 --- a/bin/tests/system/ixfr/ns3/subtest1.db +++ /dev/null @@ -1,22 +0,0 @@ -; Copyright (C) Internet Systems Consortium, Inc. ("ISC") -; -; 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 http://mozilla.org/MPL/2.0/. -; -; See the COPYRIGHT file distributed with this work for additional -; information regarding copyright ownership. - -$ORIGIN sub.test. -$TTL 15 -sub.test. 15 IN SOA ns1.test. hostmaster.test. ( - 3 ; serial - 3H ; refresh - 15 ; retry - 1w ; expire - 3h ; minimum - ) - IN NS ns1.test. - IN NS ns2.test. -host3 IN A 192.168.10.123 -host4 IN A 192.168.10.24 diff --git a/bin/tests/system/ixfr/ns4/named.conf.in b/bin/tests/system/ixfr/ns4/named.conf.in index be96c1191d..ef70229ad9 100644 --- a/bin/tests/system/ixfr/ns4/named.conf.in +++ b/bin/tests/system/ixfr/ns4/named.conf.in @@ -37,6 +37,7 @@ view "primary" { type secondary; file "mytest.db"; primaries { 10.53.0.3; }; + max-ixfr-ratio unlimited; }; zone "sub.test" IN { type secondary; diff --git a/bin/tests/system/ixfr/setup.sh b/bin/tests/system/ixfr/setup.sh index dc3b5acb78..7b6b8ff5cb 100644 --- a/bin/tests/system/ixfr/setup.sh +++ b/bin/tests/system/ixfr/setup.sh @@ -12,35 +12,56 @@ SYSTEMTESTTOP=.. . $SYSTEMTESTTOP/conf.sh -cat <ns1/named.conf -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; -}; - -key rndc_key { - secret "1234abcd8765"; - algorithm hmac-sha256; -}; - -controls { - inet 10.53.0.1 port ${CONTROLPORT} allow { any; } keys { rndc_key; }; -}; -EOF +$SHELL clean.sh +copy_setports ns1/named.conf.in ns1/named.conf copy_setports ns3/named.conf.in ns3/named.conf copy_setports ns4/named.conf.in ns4/named.conf copy_setports ns5/named.conf.in ns5/named.conf -# Setup initial db files for ns3 -cp ns3/mytest0.db ns3/mytest.db -cp ns3/subtest0.db ns3/subtest.db +# Set up db files for zone "test" - this is a series of four +# versions of the zone, the second and third having small changes +# and the fourth having a large one. + +testdb () { + cat << EOF +\$ORIGIN $1 +\$TTL 15 +@ 15 IN SOA ns1.test. hostmaster.test. ( + $2 ; serial + 3H ; refresh + 15 ; retry + 1w ; expire + 3h ; minimum + ) + IN NS ns1.test. + IN NS ns2.test. + IN NS ns5.test. +ns1 IN A 10.53.0.3 +ns2 IN A 10.53.0.4 +ns5 IN A 10.53.0.5 +EOF + + i=0 + while [ $i -lt $3 ]; do + echo "host$i IN A 192.0.2.$i" + i=$((i+1)) + done +} + +testdb test. 1 60 > ns3/mytest.db +testdb test. 2 61 > ns3/mytest1.db +testdb test. 3 62 > ns3/mytest2.db +testdb test. 4 0 > ns3/mytest3.db + +# Set up similar db files for sub.test, which will have IXFR disabled +testdb sub.test. 1 60 > ns3/subtest.db +testdb sub.test. 3 61 > ns3/subtest1.db + +# Set up a large zone +i=0 $SHELL ../genzone.sh 3 > ns3/large.db -awk 'END { for (i = 0; i < 10000; i++) printf("record%d 10 IN TXT this is record %d\n", i, i) }' < /dev/null >> ns3/large.db +while [ $i -lt 10000 ]; do + echo "record$i 10 IN TXT this is record %i" >> ns3/large.db + i=$((i+1)) +done diff --git a/bin/tests/system/ixfr/tests.sh b/bin/tests/system/ixfr/tests.sh index 571d0a403a..853ca112d7 100644 --- a/bin/tests/system/ixfr/tests.sh +++ b/bin/tests/system/ixfr/tests.sh @@ -18,6 +18,12 @@ SYSTEMTESTTOP=.. . $SYSTEMTESTTOP/conf.sh +wait_for_serial() ( + $DIG $DIGOPTS "@$1" "$2" SOA > "$4" + serial=$(awk '$4 == "SOA" { print $7 }' "$4") + [ "$3" -eq "${serial:--1}" ] +) + status=0 n=0 @@ -27,6 +33,7 @@ RNDCCMD="$RNDC -p ${CONTROLPORT} -c ../common/rndc.conf -s" n=$((n+1)) echo_i "testing initial AXFR ($n)" +ret=0 $SENDCMD < dig.out.test$n - grep "SOA" dig.out.test$n > /dev/null && break - sleep 1 -done +retry_quiet 10 wait_for_serial 10.53.0.1 nil. 1 dig.out.test$n || ret=1 -$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'initial AXFR' >/dev/null || { - echo_i "failed" - status=1 -} +$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'initial AXFR' >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) n=$((n+1)) echo_i "testing successful IXFR ($n)" +ret=0 # We change the IP address of a.nil., and the TXT record at the apex. # Then we do a SOA-only update. @@ -94,17 +96,17 @@ EOF sleep 1 -$RNDCCMD 10.53.0.1 refresh nil +$RNDCCMD 10.53.0.1 refresh nil | sed 's/^/ns1 /' | cat_i sleep 2 -$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'successful IXFR' >/dev/null || { - echo_i "failed" - status=1 -} +$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'successful IXFR' >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) n=$((n+1)) echo_i "testing AXFR fallback after IXFR failure ($n)" +ret=0 # Provide a broken IXFR response and a working fallback AXFR response @@ -129,14 +131,13 @@ EOF sleep 1 -$RNDCCMD 10.53.0.1 refresh nil +$RNDCCMD 10.53.0.1 refresh nil | sed 's/^/ns1 /' | cat_i sleep 2 -$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'fallback AXFR' >/dev/null || { - echo_i "failed" - status=1 -} +$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'fallback AXFR' >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) n=$((n+1)) echo_i "testing ixfr-from-differences option ($n)" @@ -146,150 +147,84 @@ if [ $? -ne 0 ] then echo_i "named-checkzone returned failure on ns3/mytest.db" fi -# modify the primary -#echo_i "digging against primary: " -#$DIG $DIGOPTS @10.53.0.3 a host1.test. -#echo_i "digging against secondary: " -#$DIG $DIGOPTS @10.53.0.4 a host1.test. -# wait for secondary to be stable -for i in 0 1 2 3 4 5 6 7 8 9 -do - $DIG $DIGOPTS +tcp @10.53.0.4 SOA test > dig.out.test$n - grep -i "hostmaster\.test\..1" dig.out.test$n > /dev/null && break - sleep 1 -done +retry_quiet 10 wait_for_serial 10.53.0.4 test. 1 dig.out.test$n || ret=1 + +nextpart ns4/named.run > /dev/null # modify the primary cp ns3/mytest1.db ns3/mytest.db $RNDCCMD 10.53.0.3 reload | sed 's/^/ns3 /' | cat_i -#wait for primary to reload load -for i in 0 1 2 3 4 5 6 7 8 9 -do - $DIG $DIGOPTS +tcp @10.53.0.3 SOA test > dig.out.test$n - grep -i "hostmaster\.test\..2" dig.out.test$n > /dev/null && break - sleep 1 -done +# wait for primary to reload +retry_quiet 10 wait_for_serial 10.53.0.3 test. 2 dig.out.test$n || ret=1 -#wait for secondary to transfer zone -for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -do - $DIG $DIGOPTS +tcp @10.53.0.4 SOA test > dig.out.test$n - grep -i "hostmaster\.test\..2" dig.out.test$n > /dev/null && break - - # re-notify if we've been waiting a long time - if [ $i -ge 5 ]; then - $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i - fi - sleep 1 -done - -# secondary should have gotten notify and updated - -for i in 0 1 2 3 4 5 6 7 8 9 -do - INCR=`grep "test/IN/primary" ns4/named.run|grep "got incremental"|wc -l` - [ $INCR -eq 1 ] && break - sleep 1 -done -if [ $INCR -ne 1 ] -then - echo_i "failed to get incremental response" - status=1 +# wait for secondary to reload +tret=0 +retry_quiet 5 wait_for_serial 10.53.0.4 test. 2 dig.out.test$n || tret=1 +if [ $tret -eq 1 ]; then + # re-noitfy after 5 seconds, then wait another 10 + $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i + retry_quiet 10 wait_for_serial 10.53.0.4 test. 2 dig.out.test$n || ret=1 fi +wait_for_log 10 'got incremental' ns4/named.run || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + n=$((n+1)) -echo_i "testing request-ixfr option in view vs zone ($n)" +echo_i "testing 'request-ixfr no' option inheritance from view ($n)" +ret=0 # There's a view with 2 zones. In the view, "request-ixfr yes" # but in the zone "sub.test", request-ixfr no" # we want to make sure that a change to sub.test results in AXFR, while # changes to test. result in IXFR -echo_ic "this result should be AXFR" cp ns3/subtest1.db ns3/subtest.db # change to sub.test zone, should be AXFR +nextpart ns4/named.run > /dev/null $RNDCCMD 10.53.0.3 reload | sed 's/^/ns3 /' | cat_i -#wait for primary to reload zone -for i in 0 1 2 3 4 5 6 7 8 9 -do - $DIG $DIGOPTS +tcp @10.53.0.3 SOA sub.test > dig.out.test$n - grep -i "hostmaster\.test\..3" dig.out.test$n > /dev/null && break - sleep 1 -done +# wait for primary to reload +retry_quiet 10 wait_for_serial 10.53.0.3 sub.test. 3 dig.out.test$n || ret=1 -#wait for secondary to transfer zone -for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -do - $DIG $DIGOPTS +tcp @10.53.0.4 SOA sub.test > dig.out.test$n - grep -i "hostmaster\.test\..3" dig.out.test$n > /dev/null && break - - # re-notify if we've been waiting a long time - if [ $i -ge 5 ]; then - $RNDCCMD 10.53.0.3 notify sub.test | set 's/^/ns3 /' | cat_i - fi - sleep 1 -done - -echo_ic "this result should be AXFR" -for i in 0 1 2 3 4 5 6 7 8 9 -do - NONINCR=`grep 'sub\.test/IN/primary' ns4/named.run|grep "got nonincremental" | wc -l` - [ $NONINCR -eq 2 ] && break - sleep 1 -done -if [ $NONINCR -ne 2 ] -then - echo_ic "failed to get nonincremental response in 2nd AXFR test" - - echo_i "failed" - status=1 -else - echo_ic "success: AXFR it was" +# wait for secondary to reload +tret=0 +retry_quiet 5 wait_for_serial 10.53.0.4 sub.test. 3 dig.out.test$n || tret=1 +if [ $tret -eq 1 ]; then + # re-noitfy after 5 seconds, then wait another 10 + $RNDCCMD 10.53.0.3 notify sub.test | set 's/^/ns3 /' | cat_i + retry_quiet 10 wait_for_serial 10.53.0.4 sub.test. 3 dig.out.test$n || ret=1 fi -echo_ic "this result should be IXFR" -cp ns3/mytest2.db ns3/mytest.db # change to test zone, should be IXFR -$RNDCCMD 10.53.0.3 reload | sed 's/^/ns3 /' | cat_i - -# wait for primary to reload zone -for i in 0 1 2 3 4 5 6 7 8 9 -do - $DIG +tcp -p 5300 @10.53.0.3 SOA test > dig.out.test$n - grep -i "hostmaster\.test\..4" dig.out.test$n > /dev/null && break - sleep 1 -done - -# wait for secondary to transfer zone -for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -do - $DIG $DIGOPTS +tcp @10.53.0.4 SOA test > dig.out.test$n - grep -i "hostmaster\.test\..4" dig.out.test$n > /dev/null && break - - # re-notify if we've been waiting a long time - if [ $i -ge 5 ]; then - $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i - fi - sleep 1 -done - -for i in 0 1 2 3 4 5 6 7 8 9 -do - INCR=`grep "test/IN/primary" ns4/named.run|grep "got incremental"|wc -l` - [ $INCR -eq 2 ] && break - sleep 1 -done -if [ $INCR -ne 2 ] -then - echo_ic "failed to get incremental response in 2nd IXFR test" - - echo_i "failed" - status=1 -else - echo_ic "success: IXFR it was" -fi +wait_for_log 10 'got nonincremental response' ns4/named.run || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) n=$((n+1)) +echo_i "testing 'request-ixfr yes' option inheritance from view ($n)" +ret=0 +cp ns3/mytest2.db ns3/mytest.db # change to test zone, should be IXFR +nextpart ns4/named.run > /dev/null +$RNDCCMD 10.53.0.3 reload | sed 's/^/ns3 /' | cat_i + +# wait for primary to reload +retry_quiet 10 wait_for_serial 10.53.0.3 test. 3 dig.out.test$n || ret=1 + +# wait for secondary to reload +tret=0 +retry_quiet 5 wait_for_serial 10.53.0.4 test. 3 dig.out.test$n || tret=1 +if [ $tret -eq 1 ]; then + # re-noitfy after 5 seconds, then wait another 10 + $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i + retry_quiet 10 wait_for_serial 10.53.0.4 test. 3 dig.out.test$n || ret=1 +fi + +wait_for_log 10 'got incremental response' ns4/named.run || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +ret=0 echo_i "testing DiG's handling of a multi message AXFR style IXFR response ($n)" ( (sleep 10 && kill $$) 2>/dev/null & @@ -298,9 +233,11 @@ $DIG -p ${PORT} ixfr=0 large @10.53.0.3 > dig.out.test$n kill $sub ) lines=`grep hostmaster.large dig.out.test$n | wc -l` -test ${lines:-0} -eq 2 || { echo_i "failed"; status=1; } +test ${lines:-0} -eq 2 || ret=1 messages=`sed -n 's/^;;.*messages \([0-9]*\),.*/\1/p' dig.out.test$n` -test ${messages:-0} -gt 1 || { echo_i "failed"; status=1; } +test ${messages:-0} -gt 1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) n=$((n+1)) echo_i "test 'dig +notcp ixfr=' vs 'dig ixfr= +notcp' vs 'dig ixfr=' ($n)" @@ -314,24 +251,19 @@ awk '$4 == "SOA" { if ($7 == 3) exit(0); else exit(1);}' dig.out1.test$n || ret= # Should be incremental transfer. $DIG $DIGOPTS ixfr=1 test @10.53.0.4 > dig.out3.test$n || ret=1 awk '$4 == "SOA" { soacnt++} END { if (soacnt == 6) exit(0); else exit(1);}' dig.out3.test$n || ret=1 -if [ ${ret} != 0 ]; then - echo_i "failed" - status=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +# make sure ns5 has transfered the zone +# wait for secondary to reload +tret=0 +retry_quiet 5 wait_for_serial 10.53.0.5 test. 4 dig.out.test$n || tret=1 +if [ $tret -eq 1 ]; then + # re-noitfy after 5 seconds, then wait another 10 + $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i + retry_quiet 10 wait_for_serial 10.53.0.5 test. 3 dig.out.test$n || ret=1 fi -# wait for secondary to transfer zone -for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -do - $DIG $DIGOPTS +tcp @10.53.0.5 SOA test > dig.out.test$n - grep -i "hostmaster\.test\..4" dig.out.test$n > /dev/null && break - - # re-notify if we've been waiting a long time - if [ $i -ge 5 ]; then - $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i - fi - sleep 1 -done - n=$((n+1)) echo_i "test 'provide-ixfr no;' (serial < current) ($n)" ret=0 @@ -342,32 +274,6 @@ $DIG $DIGOPTS ixfr=1 test @10.53.0.5 > dig.out1.test$n || ret=1 $DIG $DIGOPTS ixfr=1 +notcp test @10.53.0.5 > dig.out2.test$n || ret=1 awk '$4 == "SOA" { soacnt++} END {if (soacnt == 2) exit(0); else exit(1);}' dig.out1.test$n || ret=1 awk '$4 == "SOA" { soacnt++} END {if (soacnt == 1) exit(0); else exit(1);}' dig.out2.test$n || ret=1 -msg="IXFR delta response disabled due to 'provide-ixfr no;' being set" -nextpart ns5/named.run | grep "$msg" > /dev/null || ret=1 -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status+ret)) - -n=$((n+1)) -echo_i "test 'provide-ixfr no;' (serial = current) ($n)" -ret=0 -# Should be "AXFR style" response -$DIG $DIGOPTS ixfr=3 test @10.53.0.5 > dig.out1.test$n || ret=1 -# Should be "switch to TCP" response -$DIG $DIGOPTS ixfr=3 +notcp test @10.53.0.5 > dig.out2.test$n || ret=1 -awk '$4 == "SOA" { soacnt++} END {if (soacnt == 1) exit(0); else exit(1);}' dig.out1.test$n || ret=1 -awk '$4 == "SOA" { soacnt++} END {if (soacnt == 1) exit(0); else exit(1);}' dig.out2.test$n || ret=1 -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status+ret)) - -n=$((n+1)) -echo_i "test 'provide-ixfr no;' (serial > current) ($n)" -ret=0 -# Should be "AXFR style" response -$DIG $DIGOPTS ixfr=4 test @10.53.0.5 > dig.out1.test$n || ret=1 -# Should be "switch to TCP" response -$DIG $DIGOPTS ixfr=4 +notcp test @10.53.0.5 > dig.out2.test$n || ret=1 -awk '$4 == "SOA" { soacnt++} END {if (soacnt == 1) exit(0); else exit(1);}' dig.out1.test$n || ret=1 -awk '$4 == "SOA" { soacnt++} END {if (soacnt == 1) exit(0); else exit(1);}' dig.out2.test$n || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -376,32 +282,51 @@ echo_i "checking whether dig calculates IXFR statistics correctly ($n)" ret=0 $DIG $DIGOPTS +noedns +stat -b 10.53.0.4 @10.53.0.4 test. ixfr=2 > dig.out1.test$n get_dig_xfer_stats dig.out1.test$n > stats.dig -diff ixfr-stats.good stats.dig || ret=1 +diff ixfr-stats.good stats.dig > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) # Note: in the next two tests, we use ns4 logs for checking both incoming and # outgoing transfer statistics as ns4 is both a secondary server (for ns3) and a # primary server (for dig queries from the previous test) for "test". + +_wait_for_stats () { + get_named_xfer_stats ns4/named.run "$1" test "$2" > "$3" + diff ixfr-stats.good "$3" > /dev/null || return 1 + return 0 +} + n=$((n+1)) echo_i "checking whether named calculates incoming IXFR statistics correctly ($n)" ret=0 -get_named_xfer_stats ns4/named.run 10.53.0.3 test "Transfer completed" > stats.incoming -diff ixfr-stats.good stats.incoming || ret=1 +retry_quiet 10 _wait_for_stats 10.53.0.3 "Transfer completed" stats.incoming if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) n=$((n+1)) echo_i "checking whether named calculates outgoing IXFR statistics correctly ($n)" -ret=1 -for i in 0 1 2 3 4 5 6 7 8 9; do - get_named_xfer_stats ns4/named.run 10.53.0.4 test "IXFR ended" > stats.outgoing - if diff ixfr-stats.good stats.outgoing > /dev/null; then - ret=0 - break - fi - sleep 1 -done +retry_quiet 10 _wait_for_stats 10.53.0.4 "IXFR ended" stats.outgoing +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +ret=0 +echo_i "testing fallback to AXFR when max-ixfr-ratio is exceeded ($n)" +nextpart ns4/named.run > /dev/null + +cp ns3/mytest3.db ns3/mytest.db # change to test zone, too big for IXFR +$RNDCCMD 10.53.0.3 reload | sed 's/^/ns3 /' | cat_i + +# wait for secondary to reload +tret=0 +retry_quiet 5 wait_for_serial 10.53.0.4 test. 4 dig.out.test$n || tret=1 +if [ $tret -eq 1 ]; then + # re-noitfy after 5 seconds, then wait another 10 + $RNDCCMD 10.53.0.3 notify test | set 's/^/ns3 /' | cat_i + retry_quiet 10 wait_for_serial 10.53.0.4 test. 4 dig.out.test$n || ret=1 +fi + +wait_for_log 10 'got nonincremental response' ns4/named.run || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) diff --git a/bin/tests/system/run.sh b/bin/tests/system/run.sh old mode 100644 new mode 100755 diff --git a/doc/misc/master.zoneopt b/doc/misc/master.zoneopt index bb3d6281bd..f9903b34e8 100644 --- a/doc/misc/master.zoneopt +++ b/doc/misc/master.zoneopt @@ -35,6 +35,7 @@ zone [ ] { key-directory ; masterfile-format ( map | raw | text ); masterfile-style ( full | relative ); + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-transfer-idle-out ; diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index d403bfd1b8..c142586fe2 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -16,6 +16,7 @@ zone [ ] { masterfile-format ( map | raw | text ); masterfile-style ( full | relative ); masters [ port ] [ dscp ] { ( | [ port ] | [ port ] ) [ key ]; ... }; + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-refresh-time ; diff --git a/doc/misc/options b/doc/misc/options index f98abd8e78..27b047fba8 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -241,6 +241,7 @@ options { max-cache-ttl ; max-clients-per-query ; max-ixfr-log-size ( default | unlimited | ); // ancient + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; max-records ; @@ -608,6 +609,7 @@ view [ ] { max-cache-ttl ; max-clients-per-query ; max-ixfr-log-size ( default | unlimited | ); // ancient + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; max-records ; @@ -835,6 +837,7 @@ view [ ] { ... }; max-ixfr-log-size ( default | unlimited | ); // ancient + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-refresh-time ; @@ -943,6 +946,7 @@ zone [ ] { [ port ] | [ port ] ) [ key ]; ... }; max-ixfr-log-size ( default | unlimited | ); // ancient + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-refresh-time ; diff --git a/doc/misc/options.active b/doc/misc/options.active index 8542116912..8e778f135f 100644 --- a/doc/misc/options.active +++ b/doc/misc/options.active @@ -215,6 +215,7 @@ options { max-cache-size ( default | unlimited | | ); max-cache-ttl ; max-clients-per-query ; + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; max-records ; @@ -545,6 +546,7 @@ view [ ] { max-cache-size ( default | unlimited | | ); max-cache-ttl ; max-clients-per-query ; + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-ncache-ttl ; max-records ; @@ -755,6 +757,7 @@ view [ ] { | [ port ] | [ port ] ) [ key ]; ... }; + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-refresh-time ; @@ -856,6 +859,7 @@ zone [ ] { masters [ port ] [ dscp ] { ( | [ port ] | [ port ] ) [ key ]; ... }; + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-refresh-time ; diff --git a/doc/misc/slave.zoneopt b/doc/misc/slave.zoneopt index 7bc7f35946..040f2d5b69 100644 --- a/doc/misc/slave.zoneopt +++ b/doc/misc/slave.zoneopt @@ -28,6 +28,7 @@ zone [ ] { masterfile-format ( map | raw | text ); masterfile-style ( full | relative ); masters [ port ] [ dscp ] { ( | [ port ] | [ port ] ) [ key ]; ... }; + max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; max-refresh-time ; diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 1a9a6ee168..010b9b948e 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -24,7 +24,18 @@ Known Issues New Features ~~~~~~~~~~~~ -- None. +- When a secondary server receives a large incremental zone + transfer (IXFR), it can have a negative impact on query + performance while the incremental changes are applied to + the zone. To address this, ``named`` can now + limit the size of IXFR responses it sends in response to zone + transfer requests. If an IXFR response would be larger than an + AXFR of the entire zone, it will send an AXFR resonse instead. + + This behavior is controlled by the ``max-ixfr-ratio`` + option - a percentage value representing the ratio of IXFR size + to the size of a full zone transfer. This value cannot exceed + 100%, which is also the default. [GL #1515] Removed Features ~~~~~~~~~~~~~~~~ diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 64d7aaeedf..f8e937d6d3 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -1567,6 +1567,24 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, } } + obj = NULL; + (void)cfg_map_get(options, "max-ixfr-ratio", &obj); + if (obj != NULL && cfg_obj_ispercentage(obj)) { + uint32_t percent = cfg_obj_aspercentage(obj); + if (percent == 0) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'ixfr-max-ratio' must be a nonzero " + "percentage or 'unlimited')"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_RANGE; + } + } else if (percent > 100) { + cfg_obj_log(obj, logctx, ISC_LOG_WARNING, + "'ixfr-max-ratio %d%%' exceeds 100%%", + percent); + } + } + obj = NULL; (void)cfg_map_get(options, "check-names", &obj); if (obj != NULL && !cfg_obj_islist(obj)) { diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index d00a407ec9..b79dcae0fa 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -1489,10 +1489,10 @@ dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, isc_result_t dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, - uint64_t *bytes); + uint64_t *xfrsize); /*%< * On success if 'records' is not NULL, it is set to the number of records - * in the given version of the database. If 'bytes' is not NULL, it is + * in the given version of the database. If 'xfrisize' is not NULL, it is * set to the approximate number of bytes needed to transfer the records, * counting name, TTL, type, class, and rdata for each RR. (This is meant * to be a rough approximation of the size of a full zone transfer, though @@ -1502,7 +1502,7 @@ dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, * \li 'db' is a valid zone database. * \li 'version' is NULL or a valid version. * \li 'records' is NULL or a pointer to return the record count in. - * \li 'bytes' is NULL or a pointer to return the byte count in. + * \li 'xfrsize' is NULL or a pointer to return the byte count in. * * Returns: * \li #ISC_R_SUCCESS diff --git a/lib/dns/include/dns/journal.h b/lib/dns/include/dns/journal.h index a086aaa62f..8acf9ab44e 100644 --- a/lib/dns/include/dns/journal.h +++ b/lib/dns/include/dns/journal.h @@ -190,11 +190,17 @@ dns_journal_last_serial(dns_journal_t *j); isc_result_t dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial, - uint32_t end_serial); + uint32_t end_serial, size_t *xfrsizep); /*%< * Prepare to iterate over the transactions that will bring the database * from SOA serial number 'begin_serial' to 'end_serial'. * + * If 'xfrsizep' is not NULL, then on success it will be set to the + * total size of all records in the iteration (excluding headers). This + * is meant to be a rough approximation of the size of an incremental + * zone transfer, though it does not account for DNS message overhead + * or name compression.) + * * Returns: *\li ISC_R_SUCCESS *\li ISC_R_RANGE begin_serial is outside the addressable range. diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index d61322bc9c..412ed3744c 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -2338,6 +2338,25 @@ dns_zone_setrequestixfr(dns_zone_t *zone, bool flag); * \li 'zone' to be valid. */ +uint32_t +dns_zone_getixfrratio(dns_zone_t *zone); +/*% + * Returns the zone's current IXFR ratio. + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio); +/*% + * Sets the ratio of IXFR size to zone size above which we use an AXFR + * response, expressed as a percentage. Cannot exceed 100. + * + * Requires: + * \li 'zone' to be valid. + */ + void dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method); /*% diff --git a/lib/dns/journal.c b/lib/dns/journal.c index c1e3a7a4df..bc1ad96314 100644 --- a/lib/dns/journal.c +++ b/lib/dns/journal.c @@ -221,6 +221,7 @@ typedef union { */ typedef struct { unsigned char size[4]; /*%< In bytes, excluding header. */ + unsigned char count[4]; /*%< Number of records in transaction */ unsigned char serial0[4]; /*%< SOA serial before update. */ unsigned char serial1[4]; /*%< SOA serial after update. */ } journal_rawxhdr_t; @@ -256,9 +257,9 @@ typedef struct { /*% * The in-core representation of the transaction header. */ - typedef struct { uint32_t size; + uint32_t count; uint32_t serial0; uint32_t serial1; } journal_xhdr_t; @@ -304,13 +305,13 @@ struct dns_journal { isc_offset_t offset; /*%< Current file offset */ journal_header_t header; /*%< In-core journal header */ unsigned char *rawindex; /*%< In-core buffer for journal index - * in - * on-disk format */ + * in on-disk format */ journal_pos_t *index; /*%< In-core journal index */ /*% Current transaction state (when writing). */ struct { unsigned int n_soa; /*%< Number of SOAs seen */ + unsigned int n_rr; /*%< Number of RRs to write */ journal_pos_t pos[2]; /*%< Begin/end position */ } x; @@ -323,8 +324,7 @@ struct dns_journal { uint32_t current_serial; /*%< Current SOA serial * */ isc_buffer_t source; /*%< Data from disk */ - isc_buffer_t target; /*%< Data from _fromwire check - * */ + isc_buffer_t target; /*%< Data from _fromwire check */ dns_decompress_t dctx; /*%< Dummy decompression ctx */ dns_name_t name; /*%< Current domain name */ dns_rdata_t rdata; /*%< Current rdata */ @@ -462,16 +462,18 @@ journal_read_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr) { return (result); } xhdr->size = decode_uint32(raw.size); + xhdr->count = decode_uint32(raw.count); xhdr->serial0 = decode_uint32(raw.serial0); xhdr->serial1 = decode_uint32(raw.serial1); return (ISC_R_SUCCESS); } static isc_result_t -journal_write_xhdr(dns_journal_t *j, uint32_t size, uint32_t serial0, - uint32_t serial1) { +journal_write_xhdr(dns_journal_t *j, uint32_t size, uint32_t count, + uint32_t serial0, uint32_t serial1) { journal_rawxhdr_t raw; encode_uint32(size, raw.size); + encode_uint32(count, raw.count); encode_uint32(serial0, raw.serial0); encode_uint32(serial1, raw.serial1); return (journal_write(j, &raw, sizeof(raw))); @@ -1026,7 +1028,8 @@ dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff) { dns_difftuple_t *t; isc_buffer_t buffer; void *mem = NULL; - uint64_t size; + uint64_t size = 0; + uint32_t rrcount = 0; isc_result_t result; isc_region_t used; @@ -1040,7 +1043,6 @@ dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff) { * Pass 1: determine the buffer size needed, and * keep track of SOA serial numbers. */ - size = 0; for (t = ISC_LIST_HEAD(diff->tuples); t != NULL; t = ISC_LIST_NEXT(t, link)) { if (t->rdata.type == dns_rdatatype_soa) { @@ -1089,12 +1091,15 @@ dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff) { isc_buffer_putuint16(&buffer, (uint16_t)t->rdata.length); INSIST(isc_buffer_availablelength(&buffer) >= t->rdata.length); isc_buffer_putmem(&buffer, t->rdata.data, t->rdata.length); + + rrcount++; } isc_buffer_usedregion(&buffer, &used); INSIST(used.length == size); j->x.pos[1].offset += used.length; + j->x.n_rr = rrcount; /* * Write the buffer contents to the journal file. @@ -1205,7 +1210,8 @@ dns_journal_commit(dns_journal_t *j) { * Update the transaction header. */ CHECK(journal_seek(j, j->x.pos[0].offset)); - CHECK(journal_write_xhdr(j, offset, j->x.pos[0].serial, + CHECK(journal_write_xhdr(j, offset, j->x.n_rr, + j->x.pos[0].serial, j->x.pos[1].serial)); } @@ -1355,7 +1361,7 @@ roll_forward(dns_journal_t *j, dns_db_t *db, unsigned int options) { CHECK(DNS_R_UPTODATE); } - CHECK(dns_journal_iter_init(j, db_serial, end_serial)); + CHECK(dns_journal_iter_init(j, db_serial, end_serial, NULL)); for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS; result = dns_journal_next_rr(j)) @@ -1515,7 +1521,7 @@ dns_journal_print(isc_mem_t *mctx, const char *filename, FILE *file) { start_serial = dns_journal_first_serial(j); end_serial = dns_journal_last_serial(j); - CHECK(dns_journal_iter_init(j, start_serial, end_serial)); + CHECK(dns_journal_iter_init(j, start_serial, end_serial, NULL)); for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS; result = dns_journal_next_rr(j)) @@ -1672,7 +1678,7 @@ size_buffer(isc_mem_t *mctx, isc_buffer_t *b, unsigned size) { isc_result_t dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial, - uint32_t end_serial) { + uint32_t end_serial, size_t *xfrsizep) { isc_result_t result; CHECK(journal_find(j, begin_serial, &j->it.bpos)); @@ -1681,6 +1687,41 @@ dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial, CHECK(journal_find(j, end_serial, &j->it.epos)); INSIST(j->it.epos.serial == end_serial); + if (xfrsizep != NULL) { + journal_pos_t pos = j->it.bpos; + journal_xhdr_t xhdr; + uint64_t size = 0; + uint32_t count = 0; + + /* + * We already know the beginning and ending serial + * numbers are in the journal. Scan through them, + * adding up sizes and RR counts so we can calculate + * the IXFR size. + */ + CHECK(journal_seek(j, pos.offset)); + do { + CHECK(journal_read_xhdr(j, &xhdr)); + + size += xhdr.size; + count += xhdr.count; + + result = journal_next(j, &pos); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + CHECK(result); + } while (pos.serial != end_serial); + + /* + * For each RR, subtract the length of the RR header, + * as this would not be present in IXFR messages. + * (We don't need to worry about the transaction header + * because that was already excluded from xdr.size.) + */ + *xfrsizep = size - (count * sizeof(journal_rawrrhdr_t)); + } + result = ISC_R_SUCCESS; failure: j->it.result = result; diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index ef45109600..a05a0e4113 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -441,11 +441,11 @@ typedef struct rbtdb_version { unsigned char salt[DNS_NSEC3_SALTSIZE]; /* - * records and bytes are covered by rwlock. + * records and xfrsize are covered by rwlock. */ isc_rwlock_t rwlock; uint64_t records; - uint64_t bytes; + uint64_t xfrsize; isc_rwlock_t glue_rwlock; size_t glue_table_bits; @@ -1396,7 +1396,7 @@ newversion(dns_db_t *db, dns_dbversion_t **versionp) { RWLOCK(&rbtdb->current_version->rwlock, isc_rwlocktype_read); version->records = rbtdb->current_version->records; - version->bytes = rbtdb->current_version->bytes; + version->xfrsize = rbtdb->current_version->xfrsize; RWUNLOCK(&rbtdb->current_version->rwlock, isc_rwlocktype_read); rbtdb->next_serial++; @@ -6067,18 +6067,18 @@ recordsize(rdatasetheader_t *header, unsigned int namelen) { } static void -update_recordsandbytes(bool add, rbtdb_version_t *rbtversion, - rdatasetheader_t *header, unsigned int namelen) { +update_recordsandxfrsize(bool add, rbtdb_version_t *rbtversion, + rdatasetheader_t *header, unsigned int namelen) { unsigned char *hdr = (unsigned char *)header; size_t hdrsize = sizeof(*header); RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write); if (add) { rbtversion->records += dns_rdataslab_count(hdr, hdrsize); - rbtversion->bytes += recordsize(header, namelen); + rbtversion->xfrsize += recordsize(header, namelen); } else { rbtversion->records -= dns_rdataslab_count(hdr, hdrsize); - rbtversion->bytes -= recordsize(header, namelen); + rbtversion->xfrsize -= recordsize(header, namelen); } RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write); } @@ -6491,9 +6491,9 @@ find_header: } newheader->next = topheader->next; if (rbtversion != NULL && !header_nx) { - update_recordsandbytes(false, rbtversion, - header, - nodename->length); + update_recordsandxfrsize(false, rbtversion, + header, + nodename->length); } free_rdataset(rbtdb, rbtdb->common.mctx, header); } else { @@ -6544,9 +6544,9 @@ find_header: } } if (rbtversion != NULL && !header_nx) { - update_recordsandbytes(false, rbtversion, - header, - nodename->length); + update_recordsandxfrsize(false, rbtversion, + header, + nodename->length); } } } else { @@ -6623,8 +6623,8 @@ find_header: } if (rbtversion != NULL && !newheader_nx) { - update_recordsandbytes(true, rbtversion, newheader, - nodename->length); + update_recordsandxfrsize(true, rbtversion, newheader, + nodename->length); } /* @@ -7118,8 +7118,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, * to additional info. We need to clear these fields * to avoid having duplicated references. */ - update_recordsandbytes(true, rbtversion, newheader, - nodename->length); + update_recordsandxfrsize(true, rbtversion, newheader, + nodename->length); } else if (result == DNS_R_NXRRSET) { /* * This subtraction would remove all of the rdata; @@ -7155,8 +7155,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, * topheader. */ INSIST(rbtversion->serial >= topheader->serial); - update_recordsandbytes(false, rbtversion, header, - nodename->length); + update_recordsandxfrsize(false, rbtversion, header, + nodename->length); if (topheader_prev != NULL) { topheader_prev->next = newheader; } else { @@ -7520,8 +7520,8 @@ rbt_datafixer(dns_rbtnode_t *rbtnode, void *base, size_t filesize, void *arg, } } - update_recordsandbytes(true, rbtdb->current_version, header, - rbtnode->fullnamelen); + update_recordsandxfrsize(true, rbtdb->current_version, header, + rbtnode->fullnamelen); } /* We're done deserializing; clear fullnamelen */ @@ -8142,7 +8142,7 @@ getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash, static isc_result_t getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, - uint64_t *bytes) { + uint64_t *xfrsize) { dns_rbtdb_t *rbtdb; isc_result_t result = ISC_R_SUCCESS; rbtdb_version_t *rbtversion = version; @@ -8162,8 +8162,8 @@ getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, *records = rbtversion->records; } - if (bytes != NULL) { - *bytes = rbtversion->bytes; + if (xfrsize != NULL) { + *xfrsize = rbtversion->xfrsize; } RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_read); RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); @@ -8805,7 +8805,7 @@ dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type, } rbtdb->current_version->records = 0; - rbtdb->current_version->bytes = 0; + rbtdb->current_version->xfrsize = 0; rbtdb->future_version = NULL; ISC_LIST_INIT(rbtdb->open_versions); /* diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 040e8d1280..88aaa682ef 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1221,6 +1221,7 @@ dns_zone_getgluecachestats dns_zone_getidlein dns_zone_getidleout dns_zone_getincludes +dns_zone_getixfrratio dns_zone_getjournal dns_zone_getjournalsize dns_zone_getkasp @@ -1323,6 +1324,7 @@ dns_zone_setforwardacl dns_zone_setidlein dns_zone_setidleout dns_zone_setisself +dns_zone_setixfrratio dns_zone_setjournal dns_zone_setjournalsize dns_zone_setkasp diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index a46b3d9b74..d218f747d6 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -1529,10 +1529,10 @@ maybe_free(dns_xfrin_ctx_t *xfr) { xfrin_log(xfr, ISC_LOG_INFO, "Transfer completed: %d messages, %d records, " "%" PRIu64 " bytes, " - "%u.%03u secs (%u bytes/sec)", + "%u.%03u secs (%u bytes/sec) (serial %u)", xfr->nmsg, xfr->nrecs, xfr->nbytes, (unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000), - (unsigned int)persec); + (unsigned int)persec, xfr->end_serial); if (xfr->socket != NULL) { isc_socket_detach(&xfr->socket); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 029311c52e..656bbf74a0 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -407,6 +407,7 @@ struct dns_zone { * whether ixfr is requested */ bool requestixfr; + uint32_t ixfr_ratio; /*% * whether EDNS EXPIRE is requested @@ -1128,6 +1129,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->sourceserial = 0; zone->sourceserialset = false; zone->requestixfr = true; + zone->ixfr_ratio = 100; zone->requestexpire = true; ISC_LIST_INIT(zone->rss_events); ISC_LIST_INIT(zone->rss_post); @@ -15741,7 +15743,7 @@ sync_secure_journal(dns_zone_t *zone, dns_zone_t *raw, dns_journal_t *journal, return (DNS_R_UNCHANGED); } - CHECK(dns_journal_iter_init(journal, start, end)); + CHECK(dns_journal_iter_init(journal, start, end, NULL)); for (result = dns_journal_first_rr(journal); result == ISC_R_SUCCESS; result = dns_journal_next_rr(journal)) { @@ -20561,6 +20563,18 @@ dns_zone_getrequestixfr(dns_zone_t *zone) { return (zone->requestixfr); } +void +dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->ixfr_ratio = ratio; +} + +uint32_t +dns_zone_getixfrratio(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->ixfr_ratio); +} + void dns_zone_setrequestexpire(dns_zone_t *zone, bool flag) { REQUIRE(DNS_ZONE_VALID(zone)); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 28dbde65ff..d7853a3443 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -92,6 +92,7 @@ static cfg_type_t cfg_type_dnstapoutput; static cfg_type_t cfg_type_dyndb; static cfg_type_t cfg_type_plugin; static cfg_type_t cfg_type_ixfrdifftype; +static cfg_type_t cfg_type_ixfrratio; static cfg_type_t cfg_type_key; static cfg_type_t cfg_type_logfile; static cfg_type_t cfg_type_logging; @@ -2203,6 +2204,8 @@ static cfg_clausedef_t zone_clauses[] = { CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_REDIRECT }, { "max-ixfr-log-size", &cfg_type_size, CFG_CLAUSEFLAG_ANCIENT }, + { "max-ixfr-ratio", &cfg_type_ixfrratio, + CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_MIRROR }, { "max-journal-size", &cfg_type_size, CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_MIRROR }, { "max-records", &cfg_type_uint32, @@ -2740,6 +2743,28 @@ static cfg_type_t cfg_type_sizeorpercent = { doc_parse_size_or_percent, &cfg_rep_string, sizeorpercent_enums }; +/*% + * An IXFR size ratio: percentage, or "unlimited". + */ + +static isc_result_t +parse_ixfrratio(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (cfg_parse_enum_or_other(pctx, type, &cfg_type_percentage, ret)); +} + +static void +doc_ixfrratio(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(type); + cfg_print_cstr(pctx, "( unlimited | "); + cfg_doc_terminal(pctx, &cfg_type_percentage); + cfg_print_cstr(pctx, " )"); +} + +static const char *ixfrratio_enums[] = { "unlimited", NULL }; +static cfg_type_t cfg_type_ixfrratio = { "ixfr_ratio", parse_ixfrratio, + NULL, doc_ixfrratio, + NULL, ixfrratio_enums }; + /*% * optional_keyvalue */ diff --git a/lib/ns/xfrout.c b/lib/ns/xfrout.c index 3227c5eaa7..7736dcc040 100644 --- a/lib/ns/xfrout.c +++ b/lib/ns/xfrout.c @@ -225,10 +225,10 @@ static rrstream_methods_t ixfr_rrstream_methods; static isc_result_t ixfr_rrstream_create(isc_mem_t *mctx, const char *journal_filename, - uint32_t begin_serial, uint32_t end_serial, + uint32_t begin_serial, uint32_t end_serial, size_t *sizep, rrstream_t **sp) { - ixfr_rrstream_t *s; isc_result_t result; + ixfr_rrstream_t *s = NULL; INSIST(sp != NULL && *sp == NULL); @@ -240,7 +240,8 @@ ixfr_rrstream_create(isc_mem_t *mctx, const char *journal_filename, CHECK(dns_journal_open(mctx, journal_filename, DNS_JOURNAL_READ, &s->journal)); - CHECK(dns_journal_iter_init(s->journal, begin_serial, end_serial)); + CHECK(dns_journal_iter_init(s->journal, begin_serial, end_serial, + sizep)); *sp = (rrstream_t *)s; return (ISC_R_SUCCESS); @@ -663,6 +664,7 @@ typedef struct { bool shuttingdown; bool poll; const char *mnemonic; /* Style of transfer */ + uint32_t end_serial; /* Serial number after XFR is done */ struct xfr_stats stats; /*%< Transfer statistics */ } xfrout_ctx_t; @@ -959,6 +961,26 @@ got_soa: current_serial = dns_soa_getserial(¤t_soa_tuple->rdata); if (reqtype == dns_rdatatype_ixfr) { + size_t jsize; + uint64_t dbsize; + + /* + * Outgoing IXFR may have been disabled for this peer + * or globally. + */ + if ((client->attributes & NS_CLIENTATTR_TCP) != 0) { + bool provide_ixfr; + + provide_ixfr = client->view->provideixfr; + if (peer != NULL) { + (void)dns_peer_getprovideixfr(peer, + &provide_ixfr); + } + if (provide_ixfr == false) { + goto axfr_fallback; + } + } + if (!have_soa) { FAILC(DNS_R_FORMERR, "IXFR request missing SOA"); } @@ -1010,7 +1032,7 @@ got_soa: if (journalfile != NULL) { result = ixfr_rrstream_create( mctx, journalfile, begin_serial, current_serial, - &data_stream); + &jsize, &data_stream); } else { result = ISC_R_NOTFOUND; } @@ -1023,6 +1045,32 @@ got_soa: goto axfr_fallback; } CHECK(result); + + result = dns_db_getsize(db, ver, NULL, &dbsize); + if (result == ISC_R_SUCCESS) { + uint32_t ratio = dns_zone_getixfrratio(zone); + if (ratio != 0 && ((100 * jsize) / dbsize) > ratio) { + data_stream->methods->destroy(&data_stream); + data_stream = NULL; + xfrout_log1(client, question_name, + question_class, ISC_LOG_DEBUG(4), + "IXFR delta size (%zu bytes) " + "exceeds the maximum ratio to " + "database size " + "(%" PRIu64 " bytes), " + "falling back to AXFR", + jsize, dbsize); + mnemonic = "AXFR-style IXFR"; + goto axfr_fallback; + } else { + xfrout_log1(client, question_name, + question_class, ISC_LOG_DEBUG(4), + "IXFR delta size (%zu bytes); " + "database size " + "(%" PRIu64 " bytes)", + jsize, dbsize); + } + } is_ixfr = true; } else { axfr_fallback: @@ -1062,6 +1110,7 @@ have_stream: (format == dns_many_answers) ? true : false, &xfr); } + xfr->end_serial = current_serial; xfr->mnemonic = mnemonic; stream = NULL; quota = NULL; @@ -1675,10 +1724,11 @@ xfrout_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) { "%s ended: " "%" PRIu64 " messages, %" PRIu64 " records, " "%" PRIu64 " bytes, " - "%u.%03u secs (%u bytes/sec)", + "%u.%03u secs (%u bytes/sec) (serial %u)", xfr->mnemonic, xfr->stats.nmsg, xfr->stats.nrecs, xfr->stats.nbytes, (unsigned int)(msecs / 1000), - (unsigned int)(msecs % 1000), (unsigned int)persec); + (unsigned int)(msecs % 1000), (unsigned int)persec, + xfr->end_serial); /* * We're done, unreference the handle and destroy the xfr