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
This commit is contained in:
Ondřej Surý 2026-05-20 18:28:15 +02:00
parent d61fef7c10
commit 739a067de8
15 changed files with 253 additions and 0 deletions

View file

@ -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";
};

View file

@ -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

View file

@ -0,0 +1,2 @@
. 518400 IN NS a.root-servers.nil.
a.root-servers.nil. 518400 IN A 10.53.0.9

View file

@ -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";
};

View file

@ -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

View file

@ -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

View file

@ -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";
};

View file

@ -0,0 +1,2 @@
. 518400 IN NS a.root-servers.nil.
a.root-servers.nil. 518400 IN A 10.53.0.9

View file

@ -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";
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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",
]
)