From 739a067de89834820372ae14171c0889f7aedc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 20 May 2026 18:28:15 +0200 Subject: [PATCH 1/2] System test for nxdomain-redirect combined with dns64 An AAAA query for a non-existent name into a view that combines nxdomain-redirect with dns64 used to abort named via the DNS64 fallback in query_nodata(). The new module exercises all three documented entry paths into query_redirect(): the authoritative NXDOMAIN path (ns7, tripping INSIST(!is_zone) in query_notfound()), the recursive NCACHENXRRSET path (ns8, tripping REQUIRE in dns_rdataset_first() on a disassociated rdataset), and the synth-from-dnssec path (ns10 validating against ns9's signed root, with a primer A query so the second AAAA reaches query_redirect() via query_coveringnsec()). ns9 serves as a neutral upstream so the cached and synthesized negatives land real NXRRSETs. Assisted-by: Claude:claude-opus-4-7 --- bin/tests/system/redirect/ns10/named.conf.j2 | 29 +++++ bin/tests/system/redirect/ns10/redirect.db | 5 + bin/tests/system/redirect/ns10/root.hints | 2 + bin/tests/system/redirect/ns7/named.conf.j2 | 24 +++++ bin/tests/system/redirect/ns7/redirect.db | 5 + bin/tests/system/redirect/ns7/root.db | 3 + bin/tests/system/redirect/ns8/named.conf.j2 | 19 ++++ bin/tests/system/redirect/ns8/root.hints | 2 + bin/tests/system/redirect/ns9/named.conf.j2 | 20 ++++ bin/tests/system/redirect/ns9/redirect.db | 5 + bin/tests/system/redirect/ns9/root.db.in | 3 + bin/tests/system/redirect/ns9/sign.sh | 27 +++++ bin/tests/system/redirect/setup.sh | 1 + .../system/redirect/tests_redirect_dns64.py | 102 ++++++++++++++++++ .../system/redirect/tests_sh_redirect.py | 6 ++ 15 files changed, 253 insertions(+) create mode 100644 bin/tests/system/redirect/ns10/named.conf.j2 create mode 100644 bin/tests/system/redirect/ns10/redirect.db create mode 100644 bin/tests/system/redirect/ns10/root.hints create mode 100644 bin/tests/system/redirect/ns7/named.conf.j2 create mode 100644 bin/tests/system/redirect/ns7/redirect.db create mode 100644 bin/tests/system/redirect/ns7/root.db create mode 100644 bin/tests/system/redirect/ns8/named.conf.j2 create mode 100644 bin/tests/system/redirect/ns8/root.hints create mode 100644 bin/tests/system/redirect/ns9/named.conf.j2 create mode 100644 bin/tests/system/redirect/ns9/redirect.db create mode 100644 bin/tests/system/redirect/ns9/root.db.in create mode 100644 bin/tests/system/redirect/ns9/sign.sh create mode 100644 bin/tests/system/redirect/tests_redirect_dns64.py diff --git a/bin/tests/system/redirect/ns10/named.conf.j2 b/bin/tests/system/redirect/ns10/named.conf.j2 new file mode 100644 index 0000000000..9e019cc517 --- /dev/null +++ b/bin/tests/system/redirect/ns10/named.conf.j2 @@ -0,0 +1,29 @@ +// NS10 — validating recursor used to drive the synth-from-dnssec entry +// into query_redirect (covering NSEC from ns9 synthesizes NXDOMAIN +// locally, then nxdomain-redirect+dns64 hit the same buggy path as ns7). + +options { + port @PORT@; + listen-on port @PORT@ { 10.53.0.10; }; + pid-file "named.pid"; + nxdomain-redirect redirect; + dnssec-validation yes; + synth-from-dnssec yes; + dns64 64:ff9b::/96 { + clients { any; }; + mapped { any; }; + suffix ::; + }; +}; + +include "trusted.conf"; + +zone "." { + type hint; + file "root.hints"; +}; + +zone "redirect" { + type primary; + file "redirect.db"; +}; diff --git a/bin/tests/system/redirect/ns10/redirect.db b/bin/tests/system/redirect/ns10/redirect.db new file mode 100644 index 0000000000..aa7dc60f3e --- /dev/null +++ b/bin/tests/system/redirect/ns10/redirect.db @@ -0,0 +1,5 @@ +$TTL 300 +@ IN SOA ns.redirect. admin.redirect. 1 3600 900 604800 86400 +@ IN NS ns.redirect. +ns IN A 10.53.0.10 +* IN A 203.0.113.1 diff --git a/bin/tests/system/redirect/ns10/root.hints b/bin/tests/system/redirect/ns10/root.hints new file mode 100644 index 0000000000..8150fabf26 --- /dev/null +++ b/bin/tests/system/redirect/ns10/root.hints @@ -0,0 +1,2 @@ +. 518400 IN NS a.root-servers.nil. +a.root-servers.nil. 518400 IN A 10.53.0.9 diff --git a/bin/tests/system/redirect/ns7/named.conf.j2 b/bin/tests/system/redirect/ns7/named.conf.j2 new file mode 100644 index 0000000000..0ceb769ac0 --- /dev/null +++ b/bin/tests/system/redirect/ns7/named.conf.j2 @@ -0,0 +1,24 @@ +// NS7 + +options { + port @PORT@; + listen-on port @PORT@ { 10.53.0.7; }; + pid-file "named.pid"; + nxdomain-redirect redirect; + dnssec-validation no; + dns64 64:ff9b::/96 { + clients { any; }; + mapped { any; }; + suffix ::; + }; +}; + +zone "." { + type primary; + file "root.db"; +}; + +zone "redirect" { + type primary; + file "redirect.db"; +}; diff --git a/bin/tests/system/redirect/ns7/redirect.db b/bin/tests/system/redirect/ns7/redirect.db new file mode 100644 index 0000000000..5d3d4352cb --- /dev/null +++ b/bin/tests/system/redirect/ns7/redirect.db @@ -0,0 +1,5 @@ +$TTL 300 +@ IN SOA ns.redirect. admin.redirect. 1 3600 900 604800 86400 +@ IN NS ns.redirect. +ns IN A 10.53.0.7 +* IN A 203.0.113.1 diff --git a/bin/tests/system/redirect/ns7/root.db b/bin/tests/system/redirect/ns7/root.db new file mode 100644 index 0000000000..f2fa9108ed --- /dev/null +++ b/bin/tests/system/redirect/ns7/root.db @@ -0,0 +1,3 @@ +. 86400 IN SOA a.root-servers.nil. hostmaster.example.net. 2019022100 1800 900 604800 86400 +. 518400 IN NS a.root-servers.nil. +a.root-servers.nil. 518400 IN A 10.53.0.7 diff --git a/bin/tests/system/redirect/ns8/named.conf.j2 b/bin/tests/system/redirect/ns8/named.conf.j2 new file mode 100644 index 0000000000..d1513b31bb --- /dev/null +++ b/bin/tests/system/redirect/ns8/named.conf.j2 @@ -0,0 +1,19 @@ +// NS8 + +options { + port @PORT@; + listen-on port @PORT@ { 10.53.0.8; }; + pid-file "named.pid"; + nxdomain-redirect redirect; + dnssec-validation no; + dns64 64:ff9b::/96 { + clients { any; }; + mapped { any; }; + suffix ::; + }; +}; + +zone "." { + type hint; + file "root.hints"; +}; diff --git a/bin/tests/system/redirect/ns8/root.hints b/bin/tests/system/redirect/ns8/root.hints new file mode 100644 index 0000000000..8150fabf26 --- /dev/null +++ b/bin/tests/system/redirect/ns8/root.hints @@ -0,0 +1,2 @@ +. 518400 IN NS a.root-servers.nil. +a.root-servers.nil. 518400 IN A 10.53.0.9 diff --git a/bin/tests/system/redirect/ns9/named.conf.j2 b/bin/tests/system/redirect/ns9/named.conf.j2 new file mode 100644 index 0000000000..1001396a44 --- /dev/null +++ b/bin/tests/system/redirect/ns9/named.conf.j2 @@ -0,0 +1,20 @@ +// NS9 — signed-root upstream for ns8 (advisor cached path) and ns10 +// (synth-from-dnssec path) + +options { + port @PORT@; + listen-on port @PORT@ { 10.53.0.9; }; + pid-file "named.pid"; + dnssec-validation no; + recursion no; +}; + +zone "." { + type primary; + file "root.db.signed"; +}; + +zone "redirect" { + type primary; + file "redirect.db"; +}; diff --git a/bin/tests/system/redirect/ns9/redirect.db b/bin/tests/system/redirect/ns9/redirect.db new file mode 100644 index 0000000000..a6ae61e964 --- /dev/null +++ b/bin/tests/system/redirect/ns9/redirect.db @@ -0,0 +1,5 @@ +$TTL 300 +@ IN SOA ns.redirect. admin.redirect. 1 3600 900 604800 86400 +@ IN NS ns.redirect. +ns IN A 10.53.0.9 +* IN A 203.0.113.1 diff --git a/bin/tests/system/redirect/ns9/root.db.in b/bin/tests/system/redirect/ns9/root.db.in new file mode 100644 index 0000000000..e512a4c3bd --- /dev/null +++ b/bin/tests/system/redirect/ns9/root.db.in @@ -0,0 +1,3 @@ +. 86400 IN SOA a.root-servers.nil. hostmaster.example.net. 2019022100 1800 900 604800 86400 +. 518400 IN NS a.root-servers.nil. +a.root-servers.nil. 518400 IN A 10.53.0.9 diff --git a/bin/tests/system/redirect/ns9/sign.sh b/bin/tests/system/redirect/ns9/sign.sh new file mode 100644 index 0000000000..4de4111b57 --- /dev/null +++ b/bin/tests/system/redirect/ns9/sign.sh @@ -0,0 +1,27 @@ +#!/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. + +. ../../conf.sh + +zone=. +infile=root.db.in +zonefile=root.db + +key1=$($KEYGEN -q -a $DEFAULT_ALGORITHM $zone) +key2=$($KEYGEN -q -a $DEFAULT_ALGORITHM -fk $zone) + +cat $infile $key1.key $key2.key >$zonefile + +$SIGNER -P -g -O full -o $zone $zonefile >sign.ns9.root.out + +keyfile_to_static_keys $key2 >../ns10/trusted.conf diff --git a/bin/tests/system/redirect/setup.sh b/bin/tests/system/redirect/setup.sh index b98c659517..bbe3a9d445 100644 --- a/bin/tests/system/redirect/setup.sh +++ b/bin/tests/system/redirect/setup.sh @@ -20,3 +20,4 @@ cp ns2/example.db.in ns2/example.db cp ns4/example.db.in ns4/example.db (cd ns3 && $SHELL sign.sh) (cd ns5 && $SHELL sign.sh) +(cd ns9 && $SHELL sign.sh) diff --git a/bin/tests/system/redirect/tests_redirect_dns64.py b/bin/tests/system/redirect/tests_redirect_dns64.py new file mode 100644 index 0000000000..7a4b121ee6 --- /dev/null +++ b/bin/tests/system/redirect/tests_redirect_dns64.py @@ -0,0 +1,102 @@ +#!/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 pytest + +import isctest + +pytestmark = pytest.mark.extra_artifacts( + [ + "dig.out.*", + "ns1/K*", + "ns1/*.signed", + "ns1/dsset-nsec3.", + "ns1/dsset-signed.", + "ns1/nsec3.db", + "ns1/signed.db", + "ns2/example.db", + "ns2/named.stats", + "ns2/redirect.db", + "ns3/K*", + "ns3/*.signed", + "ns3/dsset-nsec3.", + "ns3/dsset-signed.", + "ns3/nsec3.db", + "ns3/signed.db", + "ns4/example.db", + "ns4/named.stats", + "ns5/K*", + "ns5/dsset-*", + "ns5/*.signed", + "ns5/root.db", + "ns5/sign.ns5.*", + "ns5/signed.db", + "ns6/signed.db.signed", + "ns9/K*", + "ns9/dsset-*", + "ns9/root.db", + "ns9/root.db.signed", + "ns9/sign.ns9.*", + "ns10/trusted.conf", + ] +) + + +def _no_crash(server, qname): + # DO=0 so nxdomain-redirect is not skipped on validated upstream + # responses; the documented landing-page deployment serves + # non-DNSSEC clients. + msg = isctest.query.create(qname, "AAAA", dnssec=False) + response = isctest.query.tcp(msg, server.ip) + isctest.check.noerror(response) + + +def _alive(server): + msg = isctest.query.create("ns.redirect.", "A", dnssec=False) + response = isctest.query.tcp(msg, server.ip) + isctest.check.noerror(response) + + +def test_nxdomain_redirect_dns64_authoritative(ns7): + # Direct AAAA to a server that is authoritative for both '.' (NXDOMAIN) + # and the redirect zone (wildcard A only). Reproduces the + # INSIST(!qctx->is_zone) abort in query_notfound() entered via + # authoritative NXDOMAIN. + _no_crash(ns7, "no-exist.") + _alive(ns7) + + +def test_nxdomain_redirect_dns64_recursive(ns8): + # Recursive resolver: the upstream returns a real NOERROR-empty AAAA + # for '*.redirect.', which the resolver caches as NCACHENXRRSET. + # Reproduces the REQUIRE(rdataset->methods != NULL) abort in + # dns_rdataset_first() reached via the disassociated rdataset on the + # second pass through redirect2(). + _no_crash(ns8, "no-exist.") + _alive(ns8) + + +def test_nxdomain_redirect_dns64_synth_from_dnssec(ns10): + # Validating recursor with synth-from-dnssec. Prime the NSEC chain + # with an A query (the redirect zone serves a wildcard A, so this + # path returns successfully without entering the DNS64 fallback). A + # subsequent AAAA query for a different nonexistent name is then + # synthesized via query_coveringnsec() and reaches query_redirect() + # through the third documented entry. Same downstream bug as the + # authoritative path. + msg = isctest.query.create("prime.", "A", dnssec=False) + response = isctest.query.tcp(msg, ns10.ip) + isctest.check.noerror(response) + + _no_crash(ns10, "trigger.") + _alive(ns10) diff --git a/bin/tests/system/redirect/tests_sh_redirect.py b/bin/tests/system/redirect/tests_sh_redirect.py index 5f20aad6f2..e985527c94 100644 --- a/bin/tests/system/redirect/tests_sh_redirect.py +++ b/bin/tests/system/redirect/tests_sh_redirect.py @@ -38,6 +38,12 @@ pytestmark = pytest.mark.extra_artifacts( "ns5/sign.ns5.*", "ns5/signed.db", "ns6/signed.db.signed", + "ns9/K*", + "ns9/dsset-*", + "ns9/root.db", + "ns9/root.db.signed", + "ns9/sign.ns9.*", + "ns10/trusted.conf", ] ) From 4bfd18d08d706218400ba131f6625f6bcd7c47cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 20 May 2026 18:28:15 +0200 Subject: [PATCH 2/2] Skip DNS64 synthesis when answering a redirected response redirect2() swaps qctx->db to the redirect zone before query_nodata() runs. The DNS64 fallback there issues an A lookup for the original query name, which is out of zone for the redirect db, and the resulting query_notfound() trips INSIST(!is_zone). The cached NCACHENXRRSET variant trips a REQUIRE in dns_rdataset_first() on a disassociated rdataset. The synth-from-dnssec entry reaches the same fallback via query_coveringnsec(). Guarding the fallback with !qctx->redirected leaves the nxdomain-redirect NXRRSET answer to be served as-is. --- lib/ns/query.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ns/query.c b/lib/ns/query.c index b160171935..ca76df4f3d 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -8902,6 +8902,7 @@ query_nodata(query_ctx_t *qctx, isc_result_t res) { #endif /* ifdef dns64_bis_return_excluded_addresses */ } else if ((result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) && !ISC_LIST_EMPTY(qctx->view->dns64) && !qctx->nxrewrite && + !qctx->redirected && qctx->client->message->rdclass == dns_rdataclass_in && qctx->qtype == dns_rdatatype_aaaa) {