Merge branch '2665-qname-minimization-disabled-after-first-resolution' into 'main'

QNAME minimization is bypassed with stale zonecut in cache

Closes #2665

See merge request isc-projects/bind9!5327
This commit is contained in:
Matthijs Mekking 2021-08-16 14:41:40 +00:00
commit b98594e048
8 changed files with 318 additions and 38 deletions

View file

@ -1,3 +1,8 @@
5694. [bug] BIND looks up the deepest zone cut in cache in order
to iterate a query. When this node is stale, it may
bypass QNAME minimization. This has been fixed.
[GL #2665]
5693. [func] Restore support for reading 'timeout' and 'attempts'
options from /etc/resolv.conf, and use their values
in dig, host and nslookup. (Previously this was

View file

@ -31,6 +31,9 @@ def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
############################################################################
# Respond to a DNS query.
# For good. it serves:
@ -51,6 +54,12 @@ def logquery(type, qname):
# 8.2.6.0.1.0.0.2.ip6.arpa IN NS ns3.good
# 1.0.0.2.ip6.arpa. IN NS ns2.good
# ip6.arpa. IN NS ns2.good
#
# For stale. it serves:
# a.b. NS ns.a.b.stale.
# ns.a.b.stale. IN A 10.53.0.3
# b. NS ns.b.stale.
# ns.b.stale. IN A 10.53.0.4
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
@ -75,9 +84,9 @@ def create_response(msg):
r = dns.message.make_response(m)
r.set_rcode(NOERROR)
if lqname.endswith("1.0.0.2.ip6.arpa."):
if endswith(lqname, "1.0.0.2.ip6.arpa."):
# Direct query - give direct answer
if lqname.endswith("8.2.6.0.1.0.0.2.ip6.arpa."):
if endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
# Delegate to ns3
r.authority.append(dns.rrset.from_text("8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."))
r.additional.append(dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3"))
@ -89,7 +98,7 @@ def create_response(msg):
# NS query at the apex
r.answer.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good."))
r.flags |= dns.flags.AA
elif "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.".endswith(lqname):
elif endswith("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.", lqname):
# NODATA answer
r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
else:
@ -97,12 +106,12 @@ def create_response(msg):
r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
r.set_rcode(NXDOMAIN)
return r
elif lqname.endswith("ip6.arpa."):
elif endswith(lqname, "ip6.arpa."):
if lqname == "ip6.arpa." and rrtype == NS:
# NS query at the apex
r.answer.append(dns.rrset.from_text("ip6.arpa.", 30, IN, NS, "ns2.good."))
r.flags |= dns.flags.AA
elif "1.0.0.2.ip6.arpa.".endswith(lqname):
elif endswith("1.0.0.2.ip6.arpa.", lqname):
# NODATA answer
r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
else:
@ -110,22 +119,47 @@ def create_response(msg):
r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1"))
r.set_rcode(NXDOMAIN)
return r
elif lqname.endswith("bad."):
elif endswith(lqname, "stale."):
if endswith(lqname, "a.b.stale."):
# Delegate to ns.a.b.stale.
r.authority.append(dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale."))
r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3"))
elif endswith(lqname, "b.stale."):
# Delegate to ns.b.stale.
r.authority.append(dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale."))
r.additional.append(dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4"))
elif lqname == "stale." and rrtype == NS:
# NS query at the apex.
r.answer.append(dns.rrset.from_text("stale.", 2, IN, NS, "ns2.stale."))
r.flags |= dns.flags.AA
elif lqname == "stale." and rrtype == SOA:
# SOA query at the apex.
r.answer.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"))
r.flags |= dns.flags.AA
elif lqname == "stale.":
# NODATA answer
r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"))
else:
# NXDOMAIN
r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"))
r.set_rcode(NXDOMAIN)
return r
elif endswith(lqname, "bad."):
bad = True
suffix = "bad."
lqname = lqname[:-4]
elif lqname.endswith("ugly."):
elif endswith(lqname, "ugly."):
ugly = True
suffix = "ugly."
lqname = lqname[:-5]
elif lqname.endswith("good."):
elif endswith(lqname, "good."):
suffix = "good."
lqname = lqname[:-5]
elif lqname.endswith("slow."):
elif endswith(lqname, "slow."):
slow = True
suffix = "slow."
lqname = lqname[:-5]
elif lqname.endswith("fwd."):
elif endswith(lqname, "fwd."):
suffix = "fwd."
lqname = lqname[:-4]
else:
@ -133,7 +167,7 @@ def create_response(msg):
return r
# Good/bad/ugly differs only in how we treat non-empty terminals
if lqname.endswith("zoop.boing."):
if endswith(lqname, "zoop.boing."):
r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix))
elif lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z." and rrtype == A:
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
@ -168,9 +202,9 @@ def create_response(msg):
else:
r.authority.append(dns.rrset.from_text(suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
if bad or not \
("icky.icky.icky.ptang.zoop.boing.".endswith(lqname) or \
"many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.".endswith(lqname) or \
"a.bit.longer.ns.name.".endswith(lqname)):
(endswith("icky.icky.icky.ptang.zoop.boing.", lqname) or \
endswith("many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.", lqname) or \
endswith("a.bit.longer.ns.name.", lqname)):
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)

View file

@ -31,6 +31,9 @@ def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
############################################################################
# Respond to a DNS query.
# For good. it serves:
@ -43,6 +46,9 @@ def logquery(type, qname):
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
#
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
#
# For stale. it serves:
# a.b.stale. IN TXT peekaboo (resolver did not do qname minimization)
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
@ -69,23 +75,46 @@ def create_response(msg):
ip6req = False
if lqname.endswith("bad."):
if endswith(lqname, "bad."):
bad = True
suffix = "bad."
lqname = lqname[:-4]
elif lqname.endswith("ugly."):
elif endswith(lqname, "ugly."):
ugly = True
suffix = "ugly."
lqname = lqname[:-5]
elif lqname.endswith("good."):
elif endswith(lqname, "good."):
suffix = "good."
lqname = lqname[:-5]
elif lqname.endswith("slow."):
elif endswith(lqname, "slow."):
slow = True
suffix = "slow."
lqname = lqname[:-5]
elif lqname.endswith("8.2.6.0.1.0.0.2.ip6.arpa."):
ip6req = True
elif endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
ip6req = True
elif endswith(lqname, "a.b.stale."):
if lqname == "a.b.stale.":
if rrtype == TXT:
# Direct query.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "peekaboo"))
r.flags |= dns.flags.AA
elif rrtype == NS:
# NS a.b.
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"))
r.flags |= dns.flags.AA
elif rrtype == SOA:
# SOA a.b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
r.flags |= dns.flags.AA
else:
# NODATA.
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
else:
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
r.set_rcode(NXDOMAIN)
# NXDOMAIN.
return r
else:
r.set_rcode(REFUSED)
return r
@ -94,15 +123,15 @@ def create_response(msg):
if lqname == "zoop.boing." and rrtype == NS:
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix))
r.flags |= dns.flags.AA
elif lqname.endswith("icky.ptang.zoop.boing."):
elif endswith(lqname, "icky.ptang.zoop.boing."):
r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix))
elif "icky.ptang.zoop.boing.".endswith(lqname):
elif endswith("icky.ptang.zoop.boing.", lqname):
r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
if bad:
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
elif lqname.endswith("zoop.boing."):
elif endswith(lqname, "zoop.boing."):
r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
r.set_rcode(NXDOMAIN)
elif ip6req:

View file

@ -31,6 +31,9 @@ def logquery(type, qname):
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
def endswith(domain, labels):
return domain.endswith("." + labels) or domain == labels
############################################################################
# Respond to a DNS query.
# For good. it serves:
@ -44,6 +47,9 @@ def logquery(type, qname):
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
#
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
#
# For stale. it serves:
# a.b.stale. IN TXT hooray (resolver did do qname minimization)
############################################################################
def create_response(msg):
m = dns.message.from_wire(msg)
@ -70,23 +76,59 @@ def create_response(msg):
ip6req = False
if lqname.endswith("bad."):
if endswith(lqname, "bad."):
bad = True
suffix = "bad."
lqname = lqname[:-4]
elif lqname.endswith("ugly."):
elif endswith(lqname, "ugly."):
ugly = True
suffix = "ugly."
lqname = lqname[:-5]
elif lqname.endswith("good."):
elif endswith(lqname, "good."):
suffix = "good."
lqname = lqname[:-5]
elif lqname.endswith("slow."):
elif endswith(lqname, "slow."):
slow = True
suffix = "slow."
lqname = lqname[:-5]
elif lqname.endswith("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."):
elif endswith(lqname, "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."):
ip6req = True
elif endswith(lqname, "b.stale."):
if lqname == "a.b.stale.":
if rrtype == TXT:
# Direct query.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "hooray"))
r.flags |= dns.flags.AA
elif rrtype == NS:
# NS a.b.
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"))
r.flags |= dns.flags.AA
elif rrtype == SOA:
# SOA a.b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
r.flags |= dns.flags.AA
else:
# NODATA.
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"))
elif lqname == "b.stale.":
if rrtype == NS:
# NS b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.b.stale."))
r.additional.append(dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4"))
r.flags |= dns.flags.AA
elif rrtype == SOA:
# SOA b.
r.answer.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
r.flags |= dns.flags.AA
else:
# NODATA.
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
else:
r.authority.append(dns.rrset.from_text(lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"))
r.set_rcode(NXDOMAIN)
# NXDOMAIN.
return r
else:
r.set_rcode(REFUSED)
return r
@ -101,9 +143,9 @@ def create_response(msg):
elif lqname == "icky.ptang.zoop.boing." and rrtype == NS:
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name."+suffix))
r.flags |= dns.flags.AA
elif lqname.endswith("icky.ptang.zoop.boing."):
elif endswith(lqname, "icky.ptang.zoop.boing."):
r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
if bad or not "more.icky.icky.icky.ptang.zoop.boing.".endswith(lqname):
if bad or not endswith("more.icky.icky.icky.ptang.zoop.boing.", lqname):
r.set_rcode(NXDOMAIN)
if ugly:
r.set_rcode(FORMERR)
@ -111,7 +153,7 @@ def create_response(msg):
r.flags |= dns.flags.AA
if lqname == "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa." and rrtype == TXT:
r.answer.append(dns.rrset.from_text("test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 1, IN, TXT, "long_ip6_name"))
elif "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.".endswith(lqname):
elif endswith("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", lqname):
#NODATA answer
r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16"))
else:

View file

@ -33,3 +33,7 @@ ns2.ugly. A 10.53.0.2
fwd. NS ns2.fwd.
ns2.fwd. A 10.53.0.2
$TTL 2
stale. NS ns2.stale.
ns2.stale. A 10.53.0.2

View file

@ -392,6 +392,146 @@ grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
# ;; ANSWER SECTION:
# test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. 1 IN TXT "long_ip6_name"
grep 'ip6\.arpa.*TXT.*long_ip6_name' dig.out.test$n > /dev/null || ret=1
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
# Below are test cases for GL #2665: The QNAME minimization (if enabled) should
# also occur on the second query, after the RRsets have expired from cache.
# BIND will still have the entries in cache, but marked stale. These stale
# entries should not prevent the resolver from minimizing the QNAME.
# We query for the test domain a.b.stale. in all cases (QNAME minimization off,
# strict mode, and relaxed mode) and expect it to behave the same the second
# time when we have a stale delegation structure in cache.
n=`expr $n + 1`
echo_i "query for .stale is not minimized when qname-minimization is off ($n)"
ret=0
$CLEANQL
$RNDCCMD 10.53.0.5 flush
$DIG $DIGOPTS @10.53.0.5 txt a.b.stale. > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "a\.b\.stale\..*1.*IN.*TXT.*peekaboo" dig.out.test$n > /dev/null || ret=1
sleep 1
echo "TXT a.b.stale." | $DIFF ans2/query.log - > /dev/null || ret=1
echo "TXT a.b.stale." | $DIFF ans3/query.log - > /dev/null || ret=1
test -f ans4/query.log && ret=1
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "query for .stale is properly minimized when qname-minimization is in strict mode ($n)"
ret=0
$CLEANQL
$RNDCCMD 10.53.0.6 flush
$DIG $DIGOPTS @10.53.0.6 txt a.b.stale. > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
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.b.stale.
ADDR ns2.stale.
NS b.stale.
NS stale.
__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.b.stale.
NS 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
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "query for .stale is properly minimized when qname-minimization is in relaxed mode ($n)"
ret=0
$CLEANQL
$RNDCCMD 10.53.0.7 flush
$DIG $DIGOPTS @10.53.0.7 txt a.b.stale. > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
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 _.b.stale.
ADDR ns.b.stale.
ADDR ns2.stale.
__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.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
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
echo_i "sleep 2, allow entries in cache to go stale"
sleep 2
n=`expr $n + 1`
echo_i "query for .stale is not minimized when qname-minimization is off (stale cache) ($n)"
ret=0
$CLEANQL
$DIG $DIGOPTS @10.53.0.5 txt a.b.stale. > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "a\.b\.stale\..*1.*IN.*TXT.*peekaboo" dig.out.test$n > /dev/null || ret=1
sleep 1
echo "TXT a.b.stale." | $DIFF ans2/query.log - > /dev/null || ret=1
echo "TXT a.b.stale." | $DIFF ans3/query.log - > /dev/null || ret=1
test -f ans4/query.log && ret=1
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "query for .stale is properly minimized when qname-minimization is in strict mode (stale cache) ($n)"
ret=0
$CLEANQL
$DIG $DIGOPTS @10.53.0.6 txt a.b.stale. > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
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
NS b.stale.
NS stale.
__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
NS 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
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "query for .stale is properly minimized when qname-minimization is in relaxed mode (stale cache) ($n)"
ret=0
$CLEANQL
$DIG $DIGOPTS @10.53.0.7 txt a.b.stale. > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
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 _.b.stale.
__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
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
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -82,3 +82,7 @@ Bug Fixes
When the affinity is not set, tests show a slight dip in the authoritative
performance of around 5% (ranging from 3.8% to 7.8%), but
the recursive performance is now consistently improved. :gl:`#2822`
- When following QNAME minimization, BIND could use a stale zonecut from cache
to resolve the query, resulting in a non-minimized query. This has been
fixed :gl:`#2665`

View file

@ -3523,7 +3523,9 @@ find_wildcard(rbtdb_search_t *search, dns_rbtnode_t **nodep,
for (header = node->data; header != NULL; header = header->next)
{
if (header->serial <= search->serial &&
!IGNORE(header) && EXISTS(header)) {
!IGNORE(header) && EXISTS(header) &&
!ANCIENT(header))
{
break;
}
}
@ -3582,7 +3584,9 @@ find_wildcard(rbtdb_search_t *search, dns_rbtnode_t **nodep,
for (header = wnode->data; header != NULL;
header = header->next) {
if (header->serial <= search->serial &&
!IGNORE(header) && EXISTS(header)) {
!IGNORE(header) && EXISTS(header) &&
!ANCIENT(header))
{
break;
}
}
@ -4677,11 +4681,13 @@ cache_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name, void *arg) {
&header_prev)) {
/* Do nothing. */
} else if (header->type == dns_rdatatype_dname &&
EXISTS(header)) {
EXISTS(header) && !ANCIENT(header))
{
dname_header = header;
header_prev = header;
} else if (header->type == RBTDB_RDATATYPE_SIGDNAME &&
EXISTS(header)) {
EXISTS(header) && !ANCIENT(header))
{
sigdname_header = header;
header_prev = header;
} else {
@ -4751,7 +4757,7 @@ find_deepest_zonecut(rbtdb_search_t *search, dns_rbtnode_t *node,
if (check_stale_header(node, header, &locktype, lock,
search, &header_prev)) {
/* Do nothing. */
} else if (EXISTS(header)) {
} else if (EXISTS(header) && !ANCIENT(header)) {
/*
* We've found an extant rdataset. See if
* we're interested in it.
@ -5357,6 +5363,7 @@ cache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
} else if (!dcnull) {
dns_name_copy(dcname, foundname);
}
/*
* We now go looking for an NS rdataset at the node.
*/
@ -5372,8 +5379,23 @@ cache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
header_next = header->next;
if (check_stale_header(node, header, &locktype, lock, &search,
&header_prev)) {
/* Do nothing. */
} else if (EXISTS(header)) {
/*
* The function dns_rbt_findnode found us the a matching
* node for 'name' and stored the result in 'dcname'.
* This is the deepest known zonecut in our database.
* However, this node may be stale and if serve-stale
* is not enabled (in other words 'stale-answer-enable'
* is set to no), this node may not be used as a
* zonecut we know about. If so, find the deepest
* zonecut from this node up and return that instead.
*/
NODE_UNLOCK(lock, locktype);
result = find_deepest_zonecut(&search, node, nodep,
foundname, rdataset,
sigrdataset);
dns_name_copy(foundname, dcname);
goto tree_exit;
} else if (EXISTS(header) && !ANCIENT(header)) {
/*
* If we found a type we were looking for, remember
* it.