diff --git a/bin/named/config.c b/bin/named/config.c index 7dd9d9e61a..3e4ceaea53 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -185,8 +185,7 @@ options {\n\ provide-ixfr true;\n\ query-source address *;\n\ query-source-v6 address *;\n\ - qname-minimization no;\n\ - qname-minimization-strict no;\n\ + qname-minimization relaxed;\n\ recursion true;\n\ request-expire true;\n\ request-ixfr true;\n\ diff --git a/bin/named/server.c b/bin/named/server.c index c5dc11cd42..38f9df1a0f 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -4642,12 +4642,18 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "qname-minimization", &obj); INSIST(result == ISC_R_SUCCESS); - view->qminimization = cfg_obj_asboolean(obj); - - obj = NULL; - result = named_config_get(maps, "qname-minimization-strict", &obj); - INSIST(result == ISC_R_SUCCESS); - view->qmin_strict = cfg_obj_asboolean(obj); + const char * qminmode = cfg_obj_asstring(obj); + INSIST(qminmode != NULL); + if (!strcmp(qminmode, "strict")) { + view->qminimization = ISC_TRUE; + view->qmin_strict = ISC_TRUE; + } else if (!strcmp(qminmode, "relaxed")) { + view->qminimization = ISC_TRUE; + view->qmin_strict = ISC_FALSE; + } else { + view->qminimization = ISC_FALSE; + view->qmin_strict = ISC_FALSE; + } obj = NULL; result = named_config_get(maps, "auth-nxdomain", &obj); diff --git a/bin/tests/system/qname-minimization/ans2/ans.py b/bin/tests/system/qname-minimization/ans2/ans.py index 036158e5f6..1f44062d80 100755 --- a/bin/tests/system/qname-minimization/ans2/ans.py +++ b/bin/tests/system/qname-minimization/ans2/ans.py @@ -35,7 +35,9 @@ def logquery(type, qname): # Respond to a DNS query. # For good. it serves: # ns2.good. IN A 10.53.0.2 -# icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.1 +# zoop.boing.good. NS ns3.good. +# ns3.good. IN A 10.53.0.3 +# too.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.good. A 192.0.2.2 # it responds properly (with NODATA empty response) to non-empty terminals # # For slow. it works the same as for good., but each response is delayed by 400 miliseconds @@ -111,20 +113,33 @@ def create_response(msg): return r # Good/bad differs only in how we treat non-empty terminals - if lqname == "icky.icky.icky.ptang.zoop.boing." and rrtype == A: - r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.1")) + if lqname.endswith("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")) elif lqname == "" and rrtype == NS: r.answer.append(dns.rrset.from_text(suffix, 1, IN, NS, "ns2." + suffix)) elif lqname == "ns2." and rrtype == A: r.answer.append(dns.rrset.from_text("ns2."+suffix, 1, IN, A, "10.53.0.2")) elif lqname == "ns2." and rrtype == AAAA: r.answer.append(dns.rrset.from_text("ns2."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::2")) + elif lqname == "ns3." and rrtype == A: + r.answer.append(dns.rrset.from_text("ns3."+suffix, 1, IN, A, "10.53.0.3")) + elif lqname == "ns3." and rrtype == AAAA: + r.answer.append(dns.rrset.from_text("ns3."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::3")) + elif lqname == "a.bit.longer.ns.name." and rrtype == A: + r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, A, "10.53.0.4")) + elif lqname == "a.bit.longer.ns.name." and rrtype == AAAA: + r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4")) 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): + 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)): r.set_rcode(NXDOMAIN) if slow: - time.sleep(0.4) + time.sleep(0.2) return r diff --git a/bin/tests/system/qname-minimization/ans3/ans.py b/bin/tests/system/qname-minimization/ans3/ans.py new file mode 100755 index 0000000000..d767403da0 --- /dev/null +++ b/bin/tests/system/qname-minimization/ans3/ans.py @@ -0,0 +1,175 @@ +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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 http://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 +from datetime import datetime, timedelta +import time +import functools + +import dns, dns.message, dns.query +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. +# For good. it serves: +# zoop.boing.good. NS ns3.good. +# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.good. +# it responds properly (with NODATA empty response) to non-empty terminals +# +# For slow. it works the same as for good., but each response is delayed by 400 miliseconds +# +# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals +############################################################################ +def create_response(msg): + m = dns.message.from_wire(msg) + qname = m.question[0].name.to_text() + lqname = qname.lower() + labels = lqname.split('.') + + # get qtype + rrtype = m.question[0].rdtype + typename = dns.rdatatype.to_text(rrtype) + bad = False + slow = False + + # log this query + with open("query.log", "a") as f: + f.write("%s %s\n" % (typename, lqname)) + print("%s %s" % (typename, lqname), end=" ") + + r = dns.message.make_response(m) + r.set_rcode(NOERROR) + + if lqname.endswith("bad."): + bad = True + suffix = "bad." + lqname = lqname[:-4] + elif lqname.endswith("good."): + suffix = "good." + lqname = lqname[:-5] + elif lqname.endswith("slow."): + slow = True + suffix = "slow." + lqname = lqname[:-5] + else: + r.set_rcode(REFUSED) + return r + + # Good/bad differs only in how we treat non-empty terminals + if lqname == "zoop.boing." and rrtype == NS: + r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix)) + elif lqname.endswith("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): + 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) + elif "zoop.boing.".endswith(lqname): + 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) + else: + r.set_rcode(REFUSED) + + if slow: + time.sleep(0.4) + 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.3" +ip6 = "fd92:7065:b8e:ffff::3" + +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 diff --git a/bin/tests/system/qname-minimization/ans4/ans.py b/bin/tests/system/qname-minimization/ans4/ans.py new file mode 100755 index 0000000000..aab281a701 --- /dev/null +++ b/bin/tests/system/qname-minimization/ans4/ans.py @@ -0,0 +1,175 @@ +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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 http://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 +from datetime import datetime, timedelta +import time +import functools + +import dns, dns.message, dns.query +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. +# For good. it serves: +# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name. +# icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.1 +# more.icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.2 +# it responds properly (with NODATA empty response) to non-empty terminals +# +# For slow. it works the same as for good., but each response is delayed by 400 miliseconds +# +# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals +############################################################################ +def create_response(msg): + m = dns.message.from_wire(msg) + qname = m.question[0].name.to_text() + lqname = qname.lower() + labels = lqname.split('.') + + # get qtype + rrtype = m.question[0].rdtype + typename = dns.rdatatype.to_text(rrtype) + bad = False + slow = False + + # log this query + with open("query.log", "a") as f: + f.write("%s %s\n" % (typename, lqname)) + print("%s %s" % (typename, lqname), end=" ") + + r = dns.message.make_response(m) + r.set_rcode(NOERROR) + + if lqname.endswith("bad."): + bad = True + suffix = "bad." + lqname = lqname[:-4] + elif lqname.endswith("good."): + suffix = "good." + lqname = lqname[:-5] + elif lqname.endswith("slow."): + slow = True + suffix = "slow." + lqname = lqname[:-5] + else: + r.set_rcode(REFUSED) + return r + + # Good/bad differs only in how we treat non-empty terminals + if lqname == "icky.icky.icky.ptang.zoop.boing." and rrtype == A: + r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.1")) + elif lqname == "more.icky.icky.icky.ptang.zoop.boing." and rrtype == A: + r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2")) + 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)) + elif lqname.endswith("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): + r.set_rcode(NXDOMAIN) + else: + r.set_rcode(REFUSED) + + if slow: + time.sleep(0.4) + 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.4" +ip6 = "fd92:7065:b8e:ffff::4" + +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 diff --git a/bin/tests/system/qname-minimization/clean.sh b/bin/tests/system/qname-minimization/clean.sh index eaff4210bb..bd5af224a5 100644 --- a/bin/tests/system/qname-minimization/clean.sh +++ b/bin/tests/system/qname-minimization/clean.sh @@ -14,4 +14,4 @@ rm -f */named.memstats rm -f */named.run rm -f dig.out.* rm -f ns*/named.lock -rm -f ans2/query.log +rm -f ans*/query.log diff --git a/bin/tests/system/qname-minimization/ns5/named.conf.in b/bin/tests/system/qname-minimization/ns5/named.conf.in index f570b88835..4d045a58ad 100644 --- a/bin/tests/system/qname-minimization/ns5/named.conf.in +++ b/bin/tests/system/qname-minimization/ns5/named.conf.in @@ -20,8 +20,7 @@ options { listen-on { 10.53.0.5; }; listen-on-v6 { none; }; recursion yes; - qname-minimization yes; - qname-minimization-strict no; + qname-minimization disabled; querylog yes; resolver-query-timeout 30; }; diff --git a/bin/tests/system/qname-minimization/ns3/named.conf.in b/bin/tests/system/qname-minimization/ns6/named.conf.in similarity index 73% rename from bin/tests/system/qname-minimization/ns3/named.conf.in rename to bin/tests/system/qname-minimization/ns6/named.conf.in index 2a6dce9c13..661549b407 100644 --- a/bin/tests/system/qname-minimization/ns3/named.conf.in +++ b/bin/tests/system/qname-minimization/ns6/named.conf.in @@ -9,19 +9,18 @@ * information regarding copyright ownership. */ -// NS3 +// NS6 options { - query-source address 10.53.0.3; - notify-source 10.53.0.3; - transfer-source 10.53.0.3; + 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.3; }; + listen-on { 10.53.0.6; }; listen-on-v6 { none; }; recursion yes; - qname-minimization no; - qname-minimization-strict no; + qname-minimization strict; querylog yes; resolver-query-timeout 30; }; @@ -32,7 +31,7 @@ key rndc_key { }; controls { - inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; + inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; zone "." { diff --git a/bin/tests/system/qname-minimization/ns4/named.conf.in b/bin/tests/system/qname-minimization/ns7/named.conf.in similarity index 73% rename from bin/tests/system/qname-minimization/ns4/named.conf.in rename to bin/tests/system/qname-minimization/ns7/named.conf.in index 403fc0fd15..48def89b25 100644 --- a/bin/tests/system/qname-minimization/ns4/named.conf.in +++ b/bin/tests/system/qname-minimization/ns7/named.conf.in @@ -9,19 +9,18 @@ * information regarding copyright ownership. */ -// NS4 +// NS7 options { - query-source address 10.53.0.4; - notify-source 10.53.0.4; - transfer-source 10.53.0.4; + 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.4; }; + listen-on { 10.53.0.7; }; listen-on-v6 { none; }; recursion yes; - qname-minimization yes; - qname-minimization-strict yes; + qname-minimization relaxed; querylog yes; resolver-query-timeout 30; }; @@ -32,7 +31,7 @@ key rndc_key { }; controls { - inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; + inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; zone "." { diff --git a/bin/tests/system/qname-minimization/setup.sh b/bin/tests/system/qname-minimization/setup.sh index 0294ed7487..e9c67c7cb9 100644 --- a/bin/tests/system/qname-minimization/setup.sh +++ b/bin/tests/system/qname-minimization/setup.sh @@ -15,6 +15,6 @@ SYSTEMTESTTOP=.. $SHELL clean.sh copy_setports ns1/named.conf.in ns1/named.conf -copy_setports ns3/named.conf.in ns3/named.conf -copy_setports ns4/named.conf.in ns4/named.conf copy_setports ns5/named.conf.in ns5/named.conf +copy_setports ns6/named.conf.in ns6/named.conf +copy_setports ns7/named.conf.in ns7/named.conf diff --git a/bin/tests/system/qname-minimization/tests.sh b/bin/tests/system/qname-minimization/tests.sh index 84ab0a4834..bd06cdd123 100755 --- a/bin/tests/system/qname-minimization/tests.sh +++ b/bin/tests/system/qname-minimization/tests.sh @@ -14,7 +14,7 @@ SYSTEMTESTTOP=.. DIGOPTS="-p ${PORT}" RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s" -CLEANQL="rm -f ans2/query.log" +CLEANQL="rm -f ans*/query.log" status=0 n=0 @@ -22,10 +22,19 @@ n=`expr $n + 1` echo_i "query for .good is not minimized when qname-minimization is off ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.3 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.good. @10.53.0.3 > dig.out.test$n +$RNDCCMD 10.53.0.5 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.good. @10.53.0.5 > dig.out.test$n grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 -echo "A icky.icky.icky.ptang.zoop.boing.good." | diff ans2/query.log - > /dev/null || ret=1 +grep "icky.icky.icky.ptang.zoop.boing.good. 1 IN A 192.0.2.1" dig.out.test$n > /dev/null || ret=1 +cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 +A icky.icky.icky.ptang.zoop.boing.good. +A ns3.good. +AAAA ns3.good. +A a.bit.longer.ns.name.good. +AAAA a.bit.longer.ns.name.good. +__EOF +echo "A icky.icky.icky.ptang.zoop.boing.good." | diff ans3/query.log - > /dev/null || ret=1 +echo "A icky.icky.icky.ptang.zoop.boing.good." | diff ans4/query.log - > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` @@ -33,10 +42,19 @@ n=`expr $n + 1` echo_i "query for .bad is not minimized when qname-minimization is off ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.3 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.bad. @10.53.0.3 > dig.out.test$n +$RNDCCMD 10.53.0.5 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.bad. @10.53.0.5 > dig.out.test$n grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 -echo "A icky.icky.icky.ptang.zoop.boing.bad." | diff ans2/query.log - > /dev/null || ret=1 +grep "icky.icky.icky.ptang.zoop.boing.bad. 1 IN A 192.0.2.1" dig.out.test$n > /dev/null || ret=1 +cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 +A icky.icky.icky.ptang.zoop.boing.bad. +A ns3.bad. +AAAA ns3.bad. +A a.bit.longer.ns.name.bad. +AAAA a.bit.longer.ns.name.bad. +__EOF +echo "A icky.icky.icky.ptang.zoop.boing.bad." | diff ans3/query.log - > /dev/null || ret=1 +echo "A icky.icky.icky.ptang.zoop.boing.bad." | diff ans4/query.log - > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` @@ -44,10 +62,20 @@ n=`expr $n + 1` echo_i "query for .slow is not minimized when qname-minimization is off ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.3 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.slow. @10.53.0.3 > dig.out.test$n +$RNDCCMD 10.53.0.5 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.slow. @10.53.0.5 > dig.out.test$n +sleep 5 grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 -echo "A icky.icky.icky.ptang.zoop.boing.slow." | diff ans2/query.log - > /dev/null || ret=1 +grep "icky.icky.icky.ptang.zoop.boing.slow. 1 IN A 192.0.2.1" dig.out.test$n > /dev/null || ret=1 +cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 +A icky.icky.icky.ptang.zoop.boing.slow. +A ns3.slow. +AAAA ns3.slow. +A a.bit.longer.ns.name.slow. +AAAA a.bit.longer.ns.name.slow. +__EOF +echo "A icky.icky.icky.ptang.zoop.boing.slow." | diff ans3/query.log - > /dev/null || ret=1 +echo "A icky.icky.icky.ptang.zoop.boing.slow." | diff ans4/query.log - > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` @@ -55,14 +83,35 @@ n=`expr $n + 1` echo_i "query for .good is properly minimized when qname-minimization is on ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.4 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.good. @10.53.0.4 > dig.out.test$n +$RNDCCMD 10.53.0.6 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.good. @10.53.0.6 > dig.out.test$n grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "icky.icky.icky.ptang.zoop.boing.good. 1 IN A 192.0.2.1" dig.out.test$n > /dev/null || ret=1 +# Duplicated NS queries are there because we're not creating +# a separate fetch when doing qname minimization - so two +# queries running for the same name but different RRTYPE +# (A and AAAA in this case) will create separate queries +# for NSes on the way. Those will be cached though, so it +# should not be a problem cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 NS boing.good. NS zoop.boing.good. +A ns3.good. +AAAA ns3.good. +NS name.good. +NS name.good. +NS ns.name.good. +NS ns.name.good. +NS longer.ns.name.good. +NS longer.ns.name.good. +A a.bit.longer.ns.name.good. +AAAA a.bit.longer.ns.name.good. +__EOF +cat << __EOF | diff ans3/query.log - > /dev/null || ret=1 NS ptang.zoop.boing.good. NS icky.ptang.zoop.boing.good. +__EOF +cat << __EOF | diff ans4/query.log - > /dev/null || ret=1 NS icky.icky.ptang.zoop.boing.good. A icky.icky.icky.ptang.zoop.boing.good. __EOF @@ -70,31 +119,36 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` n=`expr $n + 1` -echo_i "query for .bad fails when qname-minimization is on and in strict mode ($n)" +echo_i "query for .bad fails when qname-minimization is in strict mode ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.4 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.bad. @10.53.0.4 > dig.out.test$n +$RNDCCMD 10.53.0.6 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.bad. @10.53.0.6 > dig.out.test$n grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 echo "NS boing.bad." | diff ans2/query.log - > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` n=`expr $n + 1` -echo_i "query for .bad succeds when qname-minimization is on and not in strict mode ($n)" +echo_i "query for .bad succeds when qname-minimization is in relaxed mode ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.5 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.bad. @10.53.0.5 > dig.out.test$n +$RNDCCMD 10.53.0.7 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.bad. @10.53.0.7 > dig.out.test$n grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "icky.icky.icky.ptang.zoop.boing.bad. 1 IN A 192.0.2.1" dig.out.test$n > /dev/null || ret=1 cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 NS boing.bad. -NS zoop.boing.bad. -NS ptang.zoop.boing.bad. -NS icky.ptang.zoop.boing.bad. -NS icky.icky.ptang.zoop.boing.bad. A icky.icky.icky.ptang.zoop.boing.bad. +A ns3.bad. +AAAA ns3.bad. +NS name.bad. +NS name.bad. +A a.bit.longer.ns.name.bad. +AAAA a.bit.longer.ns.name.bad. __EOF +echo "A icky.icky.icky.ptang.zoop.boing.bad." | diff ans3/query.log - > /dev/null || ret=1 +echo "A icky.icky.icky.ptang.zoop.boing.bad." | diff ans4/query.log - > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` @@ -102,14 +156,30 @@ n=`expr $n + 1` echo_i "query for .slow is properly minimized when qname-minimization is on ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.4 flush -$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.slow. @10.53.0.4 > dig.out.test$n +$RNDCCMD 10.53.0.6 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.slow. @10.53.0.6 > dig.out.test$n +sleep 5 grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "icky.icky.icky.ptang.zoop.boing.slow. 1 IN A 192.0.2.1" dig.out.test$n > /dev/null || ret=1 cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 NS boing.slow. NS zoop.boing.slow. +A ns3.slow. +AAAA ns3.slow. +NS name.slow. +NS name.slow. +NS ns.name.slow. +NS ns.name.slow. +NS longer.ns.name.slow. +NS longer.ns.name.slow. +A a.bit.longer.ns.name.slow. +AAAA a.bit.longer.ns.name.slow. +__EOF +cat << __EOF | diff ans3/query.log - > /dev/null || ret=1 NS ptang.zoop.boing.slow. NS icky.ptang.zoop.boing.slow. +__EOF +cat << __EOF | diff ans4/query.log - > /dev/null || ret=1 NS icky.icky.ptang.zoop.boing.slow. A icky.icky.icky.ptang.zoop.boing.slow. __EOF @@ -117,12 +187,13 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` n=`expr $n + 1` -echo_i "query for .ip6.arpa succeds and skips on boundaries when qname-minimization is on ($n)" +echo_i "query for .ip6.arpa succeds and skips on proper boundaries when qname-minimization is on ($n)" ret=0 $CLEANQL -$RNDCCMD 10.53.0.4 flush -$DIG $DIGOPTS -x 2001:4f8::1 @10.53.0.4 > dig.out.test$n +$RNDCCMD 10.53.0.6 flush +$DIG $DIGOPTS -x 2001:4f8::1 @10.53.0.6 > dig.out.test$n grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "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. 1 IN PTR nee.com." dig.out.test$n > /dev/null || ret=1 cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 NS 1.0.0.2.ip6.arpa. NS 8.f.4.0.1.0.0.2.ip6.arpa. @@ -134,5 +205,57 @@ __EOF if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` +n=`expr $n + 1` +echo_i "query for multiple label name skips after 3rd no-delegation response ($n)" +ret=0 +$CLEANQL +$RNDCCMD 10.53.0.6 flush +$DIG $DIGOPTS 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.good. @10.53.0.6 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "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.good. 1 IN A 192.0.2.2" dig.out.test$n > /dev/null || ret=1 +# We skipped after third no-delegation. +cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 +NS z.good. +NS y.z.good. +NS x.y.z.good. +A 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.good. +__EOF +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo_i "query for multiple label name skips after 7th label ($n)" +ret=0 +$CLEANQL +$RNDCCMD 10.53.0.6 flush +$DIG $DIGOPTS more.icky.icky.icky.ptang.zoop.boing.good. @10.53.0.6 > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "more.icky.icky.icky.ptang.zoop.boing.good. 1 IN A 192.0.2.2" dig.out.test$n > /dev/null || ret=1 +cat << __EOF | diff ans2/query.log - > /dev/null || ret=1 +NS boing.good. +NS zoop.boing.good. +A ns3.good. +AAAA ns3.good. +NS name.good. +NS name.good. +NS ns.name.good. +NS ns.name.good. +NS longer.ns.name.good. +NS longer.ns.name.good. +A a.bit.longer.ns.name.good. +AAAA a.bit.longer.ns.name.good. +__EOF +cat << __EOF | diff ans3/query.log - > /dev/null || ret=1 +NS ptang.zoop.boing.good. +NS icky.ptang.zoop.boing.good. +__EOF +# There's no NS icky.icky.ptang.zoop.boing.good. query - we skipped it. +cat << __EOF | diff ans4/query.log - > /dev/null || ret=1 +NS icky.icky.ptang.zoop.boing.good. +A more.icky.icky.icky.ptang.zoop.boing.good. +__EOF +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/lib/dns/adb.c b/lib/dns/adb.c index 044c9a9092..c163053db5 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -4029,6 +4029,14 @@ fetch_name(dns_adbname_t *adbname, isc_boolean_t start_at_zone, nameservers = &rdataset; options |= DNS_FETCHOPT_UNSHARED; } + + if (adb->view->qminimization) { + options |= DNS_FETCHOPT_QMINIMIZE; + options |= DNS_FETCHOPT_QMIN_SKIP_ON_IP6A; + if (adb->view->qmin_strict) { + options |= DNS_FETCHOPT_QMIN_STRICT; + } + } fetch = new_adbfetch(adb); if (fetch == NULL) { diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index fbd90a6689..4be589fceb 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -141,6 +141,10 @@ typedef enum { #define DNS_RESOLVER_CHECKNAMES 0x01 #define DNS_RESOLVER_CHECKNAMESFAIL 0x02 +#define DNS_QMIN_MAXLABELS 7 +#define DNS_QMIN_MAX_NO_DELEGATION 3 +#define DNS_MAX_LABELS 127 + isc_result_t dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr, diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 1898021672..312d8efa16 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -265,6 +265,7 @@ struct fetchctx { unsigned int dbucketnum; char * info; isc_mem_t * mctx; + isc_stdtime_t now; /*% Locked by appropriate bucket lock. */ fetchstate state; @@ -689,7 +690,7 @@ typedef struct respctx { isc_boolean_t ns_in_answer; /* NS may be in the answer section */ isc_boolean_t negative; /* is this a negative response? */ - isc_stdtime_t now; /* time info */ + isc_stdtime_t now; /* time info */ isc_time_t tnow; isc_time_t *finish; @@ -2542,8 +2543,8 @@ resquery_send(resquery_t *query) { fctx->timeout = ISC_FALSE; /* - * Use EDNS0, unless the caller doesn't want it, or we know that - * the remote server doesn't like it. + * Use EDNS0, unless the caller doesn't want it, or we know that the + * remote server doesn't like it. */ if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0) { @@ -4437,7 +4438,7 @@ fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client, return (ISC_R_NOMEMORY); } event->result = DNS_R_SERVFAIL; - event->qtype = fctx->type; + event->qtype = fctx->fulltype; event->db = NULL; event->node = NULL; event->rdataset = rdataset; @@ -4559,6 +4560,7 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type, fctx->minimized = isc_boolean_false; fctx->ip6arpaskip = isc_boolean_false; fctx->qmin_labels = 1; + isc_stdtime_get(&fctx->now); ISC_LIST_INIT(fctx->queries); ISC_LIST_INIT(fctx->finds); ISC_LIST_INIT(fctx->altfinds); @@ -7423,7 +7425,6 @@ rctx_respinit(isc_task_t *task, dns_dispatchevent_t *devent, TIME_NOW(&rctx->tnow); rctx->finish = &rctx->tnow; isc_stdtime_get(&rctx->now); - } /* @@ -8497,7 +8498,20 @@ rctx_answer_none(respctx_t *rctx) { } else { log_formerr(fctx, "invalid response"); } - + /* + * If we're minimizing in relaxed mode, retry with full name, + * just to be safe. The error will be logged. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "XXX %d %d", fctx->minimized, fctx->options & DNS_FETCHOPT_QMIN_STRICT); + if (fctx->minimized && + !(fctx->options & DNS_FETCHOPT_QMIN_STRICT)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "disabling qname minimization for '%s'" + " due to formerr", fctx->info); + fctx->qmin_labels = DNS_MAX_LABELS+1; + return rctx_answer_minimized(rctx); + } return (DNS_R_FORMERR); } @@ -8523,12 +8537,17 @@ rctx_answer_none(respctx_t *rctx) { * If we're doing qname minimization this is an empty non-terminal, add * the next label to query and restart it. */ - if (fctx->minimized && - (fctx->rmessage->rcode == dns_rcode_noerror || - !(fctx->options & DNS_FETCHOPT_QMIN_STRICT))) { + if (fctx->minimized && fctx->rmessage->rcode == dns_rcode_noerror) { + return rctx_answer_minimized(rctx); + } + /* + * Workaround for broken servers in relaxed mode - if we hit an + * NXDOMAIN we go straight to the full query. + */ + if (fctx->minimized && !(fctx->options & DNS_FETCHOPT_QMIN_STRICT)) { + fctx->qmin_labels = DNS_MAX_LABELS+1; return rctx_answer_minimized(rctx); } - /* * Since we're not doing a referral, we don't want to cache any * NS RRs we may have found. @@ -9079,7 +9098,7 @@ rctx_nextserver(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo, } result = dns_view_findzonecut(fctx->res->view, name, fname, - rctx->now, findoptions, + fctx->now, findoptions, ISC_TRUE, ISC_TRUE, &fctx->nameservers, NULL); @@ -10283,6 +10302,10 @@ fctx_minimize_qname(fetchctx_t *fctx) { } else if (dlabels + fctx->qmin_labels > 19) { fctx->qmin_labels = 35 - dlabels; } + } else if (dlabels + fctx->qmin_labels > DNS_QMIN_MAXLABELS) { + fctx->qmin_labels = DNS_MAX_LABELS + 1; + } else if (fctx->qmin_labels > DNS_QMIN_MAX_NO_DELEGATION) { + fctx->qmin_labels = DNS_MAX_LABELS + 1; } if (dlabels + fctx->qmin_labels < nlabels) { /* diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 2ac7146794..8c0b68e52a 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -130,6 +130,7 @@ static cfg_type_t cfg_type_optional_uint32; static cfg_type_t cfg_type_options; static cfg_type_t cfg_type_portiplist; static cfg_type_t cfg_type_printtime; +static cfg_type_t cfg_type_qminmethod; static cfg_type_t cfg_type_querysource4; static cfg_type_t cfg_type_querysource6; static cfg_type_t cfg_type_querysource; @@ -1937,8 +1938,7 @@ view_clauses[] = { { "preferred-glue", &cfg_type_astring, 0 }, { "prefetch", &cfg_type_prefetch, 0 }, { "provide-ixfr", &cfg_type_boolean, 0 }, - { "qname-minimization", &cfg_type_boolean, 0 }, - { "qname-minimization-strict", &cfg_type_boolean, 0 }, + { "qname-minimization", &cfg_type_qminmethod, 0 }, /* * Note that the query-source option syntax is different * from the other -source options. @@ -2951,6 +2951,15 @@ static cfg_type_t cfg_type_optional_keyref = { doc_optional_keyvalue, &cfg_rep_string, &key_kw }; +static const char *qminmethod_enums[] = { + "strict", "relaxed", "disabled", NULL +}; + +static cfg_type_t cfg_type_qminmethod = { + "qminmethod", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, qminmethod_enums +}; + #ifdef HAVE_GEOIP /* * "geoip" ACL element: diff --git a/util/copyrights b/util/copyrights index 2e1f6ba4d5..ddeb8eafa2 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1730,12 +1730,14 @@ ./bin/tests/system/pkcs11ssl/tests.sh SH 2014,2016,2018 ./bin/tests/system/pkcs11ssl/usepkcs11 X 2014,2018 ./bin/tests/system/qname-minimization/ans2/ans.py PYTHON 2018 +./bin/tests/system/qname-minimization/ans3/ans.py PYTHON 2018 +./bin/tests/system/qname-minimization/ans4/ans.py PYTHON 2018 ./bin/tests/system/qname-minimization/clean.sh SH 2018 ./bin/tests/system/qname-minimization/ns1/named.conf.in CONF-C 2018 ./bin/tests/system/qname-minimization/ns1/root.db ZONE 2018 -./bin/tests/system/qname-minimization/ns3/named.conf.in CONF-C 2018 -./bin/tests/system/qname-minimization/ns4/named.conf.in CONF-C 2018 ./bin/tests/system/qname-minimization/ns5/named.conf.in CONF-C 2018 +./bin/tests/system/qname-minimization/ns6/named.conf.in CONF-C 2018 +./bin/tests/system/qname-minimization/ns7/named.conf.in CONF-C 2018 ./bin/tests/system/qname-minimization/setup.sh SH 2018 ./bin/tests/system/qname-minimization/tests.sh SH 2018 ./bin/tests/system/reclimit/README TXT.BRIEF 2014,2016,2017,2018