fix: usr: QNAME minimization could leak the query type

When performing QNAME minimization, `named` now sends an NS query for the original query name, before sending the final query. This prevents the parent zone from learning the original query type, in the event that the query name is a delegation point.

For example, when looking up an address record for `example.com`, NS queries are now sent to the servers for both `com` and `example.com`, before the address query is sent to the servers for `example.com`.  Previously, an address query would have been sent to the servers for `com`.

Closes #4805

Merge branch '4805-missing-qname-ns-query-when-using-qname-minimisation' into 'main'

See merge request isc-projects/bind9!9155
This commit is contained in:
Mark Andrews 2025-03-14 02:02:52 +00:00
commit 42799ae81f
13 changed files with 202 additions and 43 deletions

View file

@ -28,6 +28,7 @@ options {
} except-from {
"example";
};
qname-minimization disabled; // Regression test for GL #4652
};
trust-anchors { };

View file

@ -552,16 +552,21 @@ sys.exit(1)'; then
$DIG $DIGOPTS @10.53.0.1 tsig. >dig.out.test$n.1 || ret=1
grep "status: NOERROR" dig.out.test$n.1 >/dev/null || ret=1
rndc_dumpdb ns1
# prime cache with NS response for QNAME minimisation
grep "$pat" ns1/named_dump.db.test$n >/dev/null || ret=1
$DIG $DIGOPTS @10.53.0.1 NS nocookie.tsig >dig.out.test$n.2 || ret=1
grep "status: NOERROR" dig.out.test$n.2 >/dev/null || ret=1
# check the disabled server response
nextpart ns1/named.run >/dev/null
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig >dig.out.test$n.2 || ret=1
grep "status: NOERROR" dig.out.test$n.2 >/dev/null || ret=1
grep 'A.10\.53\.0\.9' dig.out.test$n.2 >/dev/null || ret=1
grep 'A.10\.53\.0\.10' dig.out.test$n.2 >/dev/null || ret=1
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig >dig.out.test$n.3 || ret=1
grep "status: NOERROR" dig.out.test$n.3 >/dev/null || ret=1
grep 'A.10\.53\.0\.9' dig.out.test$n.3 >/dev/null || ret=1
grep 'A.10\.53\.0\.10' dig.out.test$n.3 >/dev/null || ret=1
nextpart ns1/named.run >named.run.test$n
count=$(grep -c ') [0-9][0-9]* NOERROR 0' named.run.test$n)
test $count -eq 2 || ret=1
count=$(grep -c '^; COOKIE: ................................' named.run.test$n)
test $count -eq 1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi

View file

@ -2179,7 +2179,7 @@ echo_i "checking RRSIG query from cache ($n)"
ret=0
dig_with_opts normalthenrrsig.secure.example. @10.53.0.4 a >/dev/null || ret=1
ans=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.4 rrsig) || ret=1
expect=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.3 rrsig | grep '^A') || ret=1
expect=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.3 rrsig | grep '^\(A\|NSEC\)') || ret=1
test "$ans" = "$expect" || ret=1
# also check that RA is set
dig_with_opts normalthenrrsig.secure.example. @10.53.0.4 rrsig >dig.out.ns4.test$n || ret=1
@ -3768,7 +3768,7 @@ status=$((status + ret))
echo_i "checking EDE code 1 for bad alg mnemonic ($n)"
ret=0
dig_with_opts @10.53.0.4 badalg.secure.example >dig.out.ns4.test$n || ret=1
grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/A)" dig.out.ns4.test$n >/dev/null || ret=1
grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/NSEC)" dig.out.ns4.test$n >/dev/null || ret=1
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
n=$((n + 1))
test "$ret" -eq 0 || echo_i "failed"

View file

@ -385,7 +385,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n.1 2>&1
grep "NOERROR" dig.out.ns3.test$n.1 >/dev/null || ret=1
grep "flags:.* ad" dig.out.ns3.test$n.1 >/dev/null || ret=1
# Sanity check: the authoritative server should have been queried.
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null || ret=1
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null || ret=1
# Reconfigure ns2 so that the zone can be mirrored on ns3.
sed '/^zone "initially-unavailable" {$/,/^};$/ {
s/10.53.0.254/10.53.0.3/
@ -403,7 +403,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n.2 2>&1
grep "NOERROR" dig.out.ns3.test$n.2 >/dev/null || ret=1
grep "flags:.* ad" dig.out.ns3.test$n.2 >/dev/null || ret=1
# Ensure the authoritative server was not queried.
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null && ret=1
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@ -434,7 +434,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n 2>&1 |
grep "NOERROR" dig.out.ns3.test$n >/dev/null || ret=1
grep "flags:.* ad" dig.out.ns3.test$n >/dev/null || ret=1
# Sanity check: the authoritative server should have been queried.
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null || ret=1
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -104,9 +104,10 @@ def create_response(msg):
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "hooray"))
elif rrtype == NS:
# NS a.b.
# This is only returned if a query for b.stale/NS has been made
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
r.additional.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.4")
)
elif rrtype == SOA:
# SOA a.b.
@ -126,7 +127,7 @@ def create_response(msg):
r.flags |= dns.flags.AA
if rrtype == A:
r.answer.append(
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.4")
)
else:
# NODATA.

View file

@ -127,12 +127,14 @@ ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS a.bit.longer.ns.name.good.
NS bit.longer.ns.name.good.
NS boing.good.
NS good.
NS longer.ns.name.good.
NS name.good.
NS ns.name.good.
NS ns3.good.
NS zoop.boing.good.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
@ -165,11 +167,13 @@ ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS a.bit.longer.ns.name.good.
NS bit.longer.ns.name.good.
NS boing.good.
NS longer.ns.name.good.
NS name.good.
NS ns.name.good.
NS ns3.good.
NS zoop.boing.good.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
@ -221,6 +225,7 @@ ADDR ns3.bad.
ADDR ns3.bad.
NS boing.bad.
NS name.bad.
NS ns3.bad.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
ADDR icky.icky.icky.ptang.zoop.boing.bad.
@ -271,6 +276,7 @@ ADDR ns3.ugly.
NS boing.ugly.
NS name.ugly.
NS name.ugly.
NS ns3.ugly.
__EOF
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans3/query.log - >/dev/null || ret=1
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans4/query.log - >/dev/null || ret=1
@ -302,11 +308,13 @@ ADDR a.bit.longer.ns.name.slow.
ADDR ns2.slow.
ADDR ns3.slow.
ADDR ns3.slow.
NS a.bit.longer.ns.name.slow.
NS bit.longer.ns.name.slow.
NS boing.slow.
NS longer.ns.name.slow.
NS name.slow.
NS ns.name.slow.
NS ns3.slow.
NS slow.
NS zoop.boing.slow.
__EOF
@ -340,6 +348,7 @@ NS 8.f.4.0.1.0.0.2.ip6.arpa.
NS 0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
NS 0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
NS 0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
NS 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
@ -362,12 +371,14 @@ ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS a.bit.longer.ns.name.good.
NS bit.longer.ns.name.good.
NS boing.good.
NS good.
NS longer.ns.name.good.
NS name.good.
NS ns.name.good.
NS ns3.good.
NS zoop.boing.good.
__EOF
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
@ -449,6 +460,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@ -457,7 +469,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
NS b.stale.
TXT a.b.stale.
__EOF
@ -476,6 +490,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@ -483,7 +498,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
TXT a.b.stale.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
@ -519,6 +536,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@ -527,7 +545,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
NS b.stale.
TXT a.b.stale.
__EOF
@ -546,6 +566,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
@ -553,7 +574,9 @@ __EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.a.b.stale.
ADDR ns.b.stale.
NS a.b.stale.
TXT a.b.stale.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done

View file

@ -729,10 +729,10 @@ if ${FEATURETEST} --enable-querytrace; then
grep "status: SERVFAIL" dig.ns5.out.${n} >/dev/null || ret=1
check_namedrun() {
nextpartpeek ns5/named.run >nextpart.out.${n}
grep 'resolving tcpalso.no-questions/A for [^:]*: empty question section, accepting it anyway as TC=1' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/A): connecting via TCP' nextpart.out.${n} >/dev/null || return 1
grep 'resolving tcpalso.no-questions/A for [^:]*: empty question section$' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/A): nextitem' nextpart.out.${n} >/dev/null || return 1
grep 'resolving tcpalso.no-questions/NS for [^:]*: empty question section, accepting it anyway as TC=1' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/NS): connecting via TCP' nextpart.out.${n} >/dev/null || return 1
grep 'resolving tcpalso.no-questions/NS for [^:]*: empty question section$' nextpart.out.${n} >/dev/null || return 1
grep '(tcpalso.no-questions/NS): nextitem' nextpart.out.${n} >/dev/null || return 1
return 0
}
retry_quiet 12 check_namedrun || ret=1

View file

@ -111,8 +111,8 @@ def test_rpz_passthru_logging():
expected_rcode=dns.rcode.NOERROR,
)
assert res_allowed_any.answer == [
dns.rrset.from_text("allowed.", 300, "IN", "NS", "ns1.allowed."),
dns.rrset.from_text("allowed.", 300, "IN", "A", "10.53.0.2"),
dns.rrset.from_text("allowed.", 300, "IN", "NS", "ns1.allowed."),
]
# The comparison above doesn't compare the TTL values, and we want to
# make sure that the "passthru" rpz doesn't cap the TTL with max-policy-ttl.

View file

@ -115,10 +115,12 @@ sleep 2
# stale for somewhere between 3500-3599 seconds.
echo_i "check rndc dump stale data.example ($n)"
rndc_dumpdb ns1 || ret=1
awk '/; stale since [0-9]*/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n \
# add in inherited owner names
awk '$1 ~ /^[0-9][0-9]*$/ { $0 = last " " $0 } $1 != ";" { last = $1 } { print }' ns1/named_dump.db.test$n >named_dump.db.test$n
awk '/; stale since [0-9]*/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
| grep "; stale since [0-9]* data\.example.*3[56]...*TXT.*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
# Also make sure the not expired data does not have a stale comment.
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n \
awk '/; authanswer/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
| grep "; authanswer longttl\.example.*[56]...*TXT.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@ -1664,16 +1666,15 @@ status=$((status + ret))
# Check that expired records are dumped.
echo_i "check rndc dump expired data.example ($n)"
ret=0
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) nodata\.example\." >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) nxdomain\.example\." >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired (awaiting cleanup) othertype\.example\." >/dev/null 2>&1 || ret=1
# add in inherited owner names
awk '$1 ~ /^[0-9][0-9]*$/ { $0 = last " " $0 } $1 != ";" { last = $1 } { print }' ns5/named_dump.db.test$n >named_dump.db.test$n
# extract expired records
awk '/; expired/ { x=$0; getline; print x, $0}' named_dump.db.test$n >expired.test$n
grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" expired.test$n >/dev/null 2>&1 || ret=1
grep "; expired (awaiting cleanup) nodata\.example\." expired.test$n >/dev/null 2>&1 || ret=1
grep "; expired (awaiting cleanup) nxdomain\.example\." expired.test$n >/dev/null 2>&1 || ret=1
# Also make sure the not expired data does not have an expired comment.
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
awk '/; authanswer/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
| grep "; authanswer longttl\.example.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -14,6 +14,8 @@ import pytest
pytestmark = pytest.mark.extra_artifacts(
[
"dig.out.*",
"expired.test*",
"named_dump.db.test*",
"rndc.out.*",
"ans*/ans.run",
"ns*/named.stats*",

View file

@ -414,10 +414,10 @@ for ns in 2 4 5 6; do
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
if [ ${synth} = yes ]; then
check_synth_cname b.wild-cname.example. dig.out.ns${ns}.test$n || ret=1
nextpart ns1/named.run | grep b.wild-cname.example/A >/dev/null && ret=1
nextpart ns1/named.run | grep b.wild-cname.example/NS >/dev/null && ret=1
else
check_nosynth_cname b.wild-cname.example. dig.out.ns${ns}.test$n || ret=1
nextpart ns1/named.run | grep b.wild-cname.example/A >/dev/null || ret=1
nextpart ns1/named.run | grep b.wild-cname.example/NS >/dev/null || ret=1
fi
grep "ns1.example.*.IN.A" dig.out.ns${ns}.test$n >/dev/null || ret=1
digcomp wildcname.out dig.out.ns${ns}.test$n || ret=1
@ -470,6 +470,7 @@ for ns in 2 4 5 6; do
check_nosynth_aaaa b.wild-2-nsec-afterdata.example. dig.out.a.ns${ns}.test$n || ret=1
#
nextpart ns1/named.run >/dev/null
sleep 1
dig_with_opts b.wild-2-nsec-afterdata.example. @10.53.0.${ns} TLSA >dig.out.ns${ns}.test$n || ret=1
check_ad_flag $ad dig.out.ns${ns}.test$n || ret=1
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
@ -531,7 +532,7 @@ for ns in 2 4 5 6; do
check_ad_flag no dig.out.ns${ns}.test$n || ret=1
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
check_nosynth_cname b.wild-cname.insecure.example dig.out.ns${ns}.test$n || ret=1
nextpart ns1/named.run | grep b.wild-cname.insecure.example/A >/dev/null || ret=1
nextpart ns1/named.run | grep b.wild-cname.insecure.example/NS >/dev/null || ret=1
grep "ns1.insecure.example.*.IN.A" dig.out.ns${ns}.test$n >/dev/null || ret=1
digcomp insecure.wildcname.out dig.out.ns${ns}.test$n || ret=1
n=$((n + 1))

View file

@ -129,6 +129,7 @@ enum {
* on ip6.arpa. */
DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if
* possible. */
DNS_FETCHOPT_QMINFETCH = 1 << 16, /*%< Qmin fetch */
/*% EDNS version bits: */
DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23,

View file

@ -401,6 +401,7 @@ struct fetchctx {
dns_rdatatype_t qmintype;
dns_fetch_t *qminfetch;
dns_rdataset_t qminrrset;
dns_rdataset_t qminsigrrset;
dns_fixedname_t qmindcfname;
dns_name_t *qmindcname;
dns_fixedname_t fwdfname;
@ -697,6 +698,9 @@ fctx__done(fetchctx_t *fctx, isc_result_t result, const char *func,
static void
resume_qmin(void *arg);
static void
clone_results(fetchctx_t *fctx);
static isc_result_t
get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
dns_rdatatype_t type, const dns_name_t *domain,
@ -1436,8 +1440,6 @@ fcount_incr(fetchctx_t *fctx, bool force) {
counter = isc_mem_get(fctx->mctx, sizeof(*counter));
*counter = (fctxcount_t){
.magic = FCTXCOUNT_MAGIC,
.count = 0,
.allowed = 0,
};
isc_mem_attach(fctx->mctx, &counter->mctx);
isc_mutex_init(&counter->lock);
@ -4138,9 +4140,11 @@ fctx_try(fetchctx_t *fctx, bool retrying) {
fetchctx_ref(fctx);
result = dns_resolver_createfetch(
fctx->res, fctx->qminname, fctx->qmintype, fctx->domain,
&fctx->nameservers, NULL, NULL, 0, options, 0, fctx->qc,
&fctx->nameservers, NULL, NULL, 0,
options | DNS_FETCHOPT_QMINFETCH, 0, fctx->qc,
fctx->gqc, fctx->loop, resume_qmin, fctx, &fctx->edectx,
&fctx->qminrrset, NULL, &fctx->qminfetch);
&fctx->qminrrset, &fctx->qminsigrrset,
&fctx->qminfetch);
if (result != ISC_R_SUCCESS) {
fetchctx_unref(fctx);
goto done;
@ -4193,6 +4197,11 @@ resume_qmin(void *arg) {
unsigned int findoptions = 0;
dns_name_t *fname = NULL, *dcname = NULL;
dns_fixedname_t ffixed, dcfixed;
dns_rdataset_t rdataset;
dns_rdataset_t sigrdataset;
dns_db_t *db = NULL;
dns_dbnode_t *node = NULL;
bool fixup_result = false;
REQUIRE(VALID_FCTX(fctx));
@ -4205,16 +4214,27 @@ resume_qmin(void *arg) {
fname = dns_fixedname_initname(&ffixed);
dcname = dns_fixedname_initname(&dcfixed);
dns_rdataset_init(&rdataset);
dns_rdataset_init(&sigrdataset);
if (resp->node != NULL) {
dns_db_attachnode(resp->db, resp->node, &node);
dns_db_detachnode(resp->db, &resp->node);
}
if (resp->db != NULL) {
dns_db_attach(resp->db, &db);
dns_db_detach(&resp->db);
}
if (dns_rdataset_isassociated(resp->rdataset)) {
dns_rdataset_clone(resp->rdataset, &rdataset);
dns_rdataset_disassociate(resp->rdataset);
}
if (dns_rdataset_isassociated(resp->sigrdataset)) {
dns_rdataset_clone(resp->sigrdataset, &sigrdataset);
dns_rdataset_disassociate(resp->sigrdataset);
}
dns_name_copy(resp->foundname, fname);
result = resp->result;
@ -4242,11 +4262,43 @@ resume_qmin(void *arg) {
case DNS_R_FORMERR:
case DNS_R_REMOTEFORMERR:
case ISC_R_FAILURE:
case ISC_R_TIMEDOUT:
if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) != 0) {
/* These results cause a hard fail in strict mode */
goto cleanup;
}
if (result == DNS_R_NXDOMAIN &&
fctx->qmin_labels == dns_name_countlabels(fctx->name))
{
LOCK(&fctx->lock);
resp = ISC_LIST_HEAD(fctx->resps);
if (resp != NULL) {
if (dns_rdataset_isassociated(&rdataset)) {
dns_rdataset_clone(&rdataset,
resp->rdataset);
}
if (dns_rdataset_isassociated(&sigrdataset) &&
resp->sigrdataset != NULL)
{
dns_rdataset_clone(&sigrdataset,
resp->sigrdataset);
}
if (db != NULL) {
dns_db_attach(db, &resp->db);
}
if (node != NULL) {
dns_db_attachnode(db, node,
&resp->node);
}
dns_name_copy(fname, resp->foundname);
clone_results(fctx);
UNLOCK(&fctx->lock);
goto cleanup;
}
UNLOCK(&fctx->lock);
}
/* ...or disable minimization in relaxed mode */
fctx->qmin_labels = DNS_NAME_MAXLABELS;
@ -4274,6 +4326,53 @@ resume_qmin(void *arg) {
{
fctx->force_qmin_warning = true;
}
/*
* We have got a CNAME or DNAME respone to the NS query
* so we are done in almost all cases.
*/
if ((result == DNS_R_CNAME || result == DNS_R_DNAME) &&
fctx->qmin_labels == dns_name_countlabels(fctx->name) &&
fctx->type != dns_rdatatype_key &&
fctx->type != dns_rdatatype_nsec &&
fctx->type != dns_rdatatype_any &&
fctx->type != dns_rdatatype_sig &&
fctx->type != dns_rdatatype_rrsig)
{
LOCK(&fctx->lock);
resp = ISC_LIST_HEAD(fctx->resps);
if (resp != NULL) {
if (dns_rdataset_isassociated(&rdataset)) {
dns_rdataset_clone(&rdataset,
resp->rdataset);
}
if (dns_rdataset_isassociated(&sigrdataset) &&
resp->sigrdataset != NULL)
{
dns_rdataset_clone(&sigrdataset,
resp->sigrdataset);
}
if (db != NULL) {
dns_db_attach(db, &resp->db);
}
if (node != NULL) {
dns_db_attachnode(db, node,
&resp->node);
}
dns_name_copy(fname, resp->foundname);
if (result == DNS_R_CNAME &&
dns_rdataset_isassociated(&rdataset) &&
fctx->type == dns_rdatatype_cname)
{
fixup_result = true;
}
clone_results(fctx);
UNLOCK(&fctx->lock);
goto cleanup;
}
UNLOCK(&fctx->lock);
}
/*
* Any other result will *not* cause a failure in strict
* mode, or cause minimization to be disabled in relaxed
@ -4320,7 +4419,7 @@ resume_qmin(void *arg) {
dns_name_copy(fname, fctx->domain);
result = fcount_incr(fctx, true);
result = fcount_incr(fctx, false);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
@ -4345,9 +4444,21 @@ resume_qmin(void *arg) {
fctx_try(fctx, true);
cleanup:
if (node != NULL) {
dns_db_detachnode(db, &node);
}
if (db != NULL) {
dns_db_detach(&db);
}
if (dns_rdataset_isassociated(&rdataset)) {
dns_rdataset_disassociate(&rdataset);
}
if (dns_rdataset_isassociated(&sigrdataset)) {
dns_rdataset_disassociate(&sigrdataset);
}
if (result != ISC_R_SUCCESS) {
/* An error occurred, tear down whole fctx */
fctx_done_unref(fctx, result);
fctx_done_unref(fctx, fixup_result ? ISC_R_SUCCESS : result);
}
fetchctx_detach(&fctx);
}
@ -4659,6 +4770,7 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
dns_rdataset_init(&fctx->nameservers);
dns_rdataset_init(&fctx->qminrrset);
dns_rdataset_init(&fctx->qminsigrrset);
dns_rdataset_init(&fctx->nsrrset);
fctx->start = isc_time_now();
@ -4746,8 +4858,12 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
/*
* Exempt prefetch queries from the fetches-per-zone quota check
* also exempt QMIN fetches as the calling fetch has already
* successfully called fcount_incr for this zone.
*/
if ((fctx->options & DNS_FETCHOPT_PREFETCH) == 0) {
if ((fctx->options & DNS_FETCHOPT_PREFETCH) == 0 &&
(fctx->options & DNS_FETCHOPT_QMINFETCH) == 0)
{
/*
* Are there too many simultaneous queries for this domain?
*/
@ -7146,7 +7262,7 @@ resume_dslookup(void *arg) {
fcount_decr(fctx);
dns_name_copy(fctx->nsname, fctx->domain);
result = fcount_incr(fctx, true);
result = fcount_incr(fctx, false);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
@ -9407,7 +9523,7 @@ rctx_referral(respctx_t *rctx) {
fctx_minimize_qname(fctx);
}
result = fcount_incr(fctx, true);
result = fcount_incr(fctx, false);
if (result != ISC_R_SUCCESS) {
rctx->result = result;
return ISC_R_COMPLETE;
@ -10333,13 +10449,13 @@ fctx_minimize_qname(fetchctx_t *fctx) {
} else if (fctx->qmin_labels < 35) {
fctx->qmin_labels = 35;
} else {
fctx->qmin_labels = nlabels;
fctx->qmin_labels = nlabels + 1;
}
} else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) {
fctx->qmin_labels = DNS_NAME_MAXLABELS;
}
if (fctx->qmin_labels < nlabels) {
if (fctx->qmin_labels <= nlabels) {
dns_rdataset_t rdataset;
dns_fixedname_t fixed;
dns_name_t *fname = dns_fixedname_initname(&fixed);
@ -10373,10 +10489,18 @@ fctx_minimize_qname(fetchctx_t *fctx) {
break;
}
break;
} while (fctx->qmin_labels < nlabels);
} while (fctx->qmin_labels <= nlabels);
}
if (fctx->qmin_labels < nlabels) {
/*
* DS lookups come from the parent zone so we don't need to do a
* NS lookup at the QNAME. If the QTYPE is NS we are not leaking
* the type if we just do the final NS lookup.
*/
if (fctx->qmin_labels < nlabels ||
(fctx->type != dns_rdatatype_ns && fctx->type != dns_rdatatype_ds &&
fctx->qmin_labels == nlabels))
{
dns_name_copy(&name, fctx->qminname);
fctx->qmintype = dns_rdatatype_ns;
fctx->minimized = true;