diff --git a/CHANGES b/CHANGES index 570edc0547..db2ac6a730 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +5242. [bug] In relaxed qname minimizatiom mode, fall back to + normal resolution when encountering a lame + delegation, and use _.domain/A queries rather + than domain/NS. [GL #1055] + 5241. [bug] Fix Ed448 private and public key ASN.1 prefix blobs. [GL #225] diff --git a/bin/tests/system/conf.sh.common b/bin/tests/system/conf.sh.common index 38e43f2abc..e32f1a28fb 100644 --- a/bin/tests/system/conf.sh.common +++ b/bin/tests/system/conf.sh.common @@ -21,7 +21,7 @@ else TESTSOCK6=false fi -TESTSOCK6="$TESTSOCK6" +export LANG=C . ${TOP}/version diff --git a/bin/tests/system/qmin/ans3/ans.py b/bin/tests/system/qmin/ans3/ans.py index 51d9ae2e7e..839e71a025 100755 --- a/bin/tests/system/qmin/ans3/ans.py +++ b/bin/tests/system/qmin/ans3/ans.py @@ -98,7 +98,7 @@ def create_response(msg): r.set_rcode(NXDOMAIN) if ugly: r.set_rcode(FORMERR) - elif "zoop.boing.".endswith(lqname): + elif lqname.endswith("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) else: diff --git a/bin/tests/system/qmin/tests.sh b/bin/tests/system/qmin/tests.sh index b67f3511e0..0ad69c45d2 100755 --- a/bin/tests/system/qmin/tests.sh +++ b/bin/tests/system/qmin/tests.sh @@ -109,7 +109,7 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` n=`expr $n + 1` -echo_i "query for .good is properly minimized when qname-minimization is on ($n)" +echo_i "query for .good is properly minimized when qname-minimization is in strict mode ($n)" ret=0 $CLEANQL $RNDCCMD 10.53.0.6 flush @@ -142,6 +142,37 @@ for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` +n=`expr $n + 1` +echo_i "query for .good is properly minimized when qname-minimization is in relaxed mode ($n)" +ret=0 +$CLEANQL +$RNDCCMD 10.53.0.7 flush +$DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.good. @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.good. 1 IN A 192.0.2.1" 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 _.boing.good. +ADDR _.zoop.boing.good. +ADDR a.bit.longer.ns.name.good. +ADDR a.bit.longer.ns.name.good. +ADDR ns2.good. +ADDR ns3.good. +ADDR ns3.good. +__EOF +cat << __EOF | $DIFF ans3/query.log - > /dev/null || ret=1 +ADDR _.ptang.zoop.boing.good. +ADDR _.icky.ptang.zoop.boing.good. +__EOF +cat << __EOF | $DIFF ans4/query.log - > /dev/null || ret=1 +ADDR _.icky.icky.ptang.zoop.boing.good. +ADDR icky.icky.icky.ptang.zoop.boing.good. +__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 .bad fails when qname-minimization is in strict mode ($n)" ret=0 @@ -171,17 +202,22 @@ grep "icky.icky.icky.ptang.zoop.boing.bad. 1 IN A 192.0.2.1" dig.out.test$n > /d sleep 1 sort ans2/query.log > ans2/query.log.sorted cat << __EOF | $DIFF ans2/query.log.sorted - > /dev/null || ret=1 +ADDR _.boing.bad. +ADDR _.zoop.boing.bad. ADDR a.bit.longer.ns.name.bad. ADDR a.bit.longer.ns.name.bad. -ADDR icky.icky.icky.ptang.zoop.boing.bad. ADDR ns2.bad. ADDR ns3.bad. ADDR ns3.bad. -NS bad. -NS boing.bad. __EOF -echo "ADDR icky.icky.icky.ptang.zoop.boing.bad." | $DIFF ans3/query.log - > /dev/null || ret=1 -echo "ADDR icky.icky.icky.ptang.zoop.boing.bad." | $DIFF ans4/query.log - > /dev/null || ret=1 +cat << __EOF | $DIFF ans3/query.log - > /dev/null || ret=1 +ADDR _.ptang.zoop.boing.bad. +ADDR _.icky.ptang.zoop.boing.bad. +__EOF +cat << __EOF | $DIFF ans4/query.log - > /dev/null || ret=1 +ADDR _.icky.icky.ptang.zoop.boing.bad. +ADDR icky.icky.icky.ptang.zoop.boing.bad. +__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` @@ -215,17 +251,17 @@ $DIG $DIGOPTS icky.icky.icky.ptang.zoop.boing.ugly. @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.ugly. 1 IN A 192.0.2.1" 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 +cat << __EOF | $DIFF ans2/query.log.sorted - > /dev/null || cat ans2/query.log.sorted +ADDR _.boing.ugly. +ADDR _.boing.ugly. ADDR a.bit.longer.ns.name.ugly. ADDR a.bit.longer.ns.name.ugly. ADDR icky.icky.icky.ptang.zoop.boing.ugly. ADDR ns2.ugly. ADDR ns3.ugly. ADDR ns3.ugly. -NS boing.ugly. -NS boing.ugly. -NS 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 diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index 53533723a6..3a074a8481 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -110,15 +110,20 @@ typedef enum { #define DNS_FETCHOPT_NOCACHED 0x00008000 /*%< Force cache update. */ #define DNS_FETCHOPT_QMINIMIZE 0x00010000 /*%< Use qname minimization. */ -#define DNS_FETCHOPT_QMIN_STRICT 0x00020000 /*%< Do not work around +#define DNS_FETCHOPT_NOFOLLOW 0x00020000 /*%< Don't follow + delegations */ +#define DNS_FETCHOPT_QMIN_STRICT 0x00040000 /*%< Do not work around servers that return errors on non-empty terminals. */ -#define DNS_FETCHOPT_QMIN_SKIP_IP6A 0x00040000 /*%< Skip some labels +#define DNS_FETCHOPT_QMIN_USE_A 0x00080000 /*%< Use A type queries + instead of NS when + doing minimization */ +#define DNS_FETCHOPT_QMIN_SKIP_IP6A 0x00100000 /*%< Skip some labels when doing qname minimization on ip6.arpa. */ -#define DNS_FETCHOPT_NOFORWARD 0x00080000 /*%< Do not use forwarders +#define DNS_FETCHOPT_NOFORWARD 0x00200000 /*%< Do not use forwarders if possible. */ /* Reserved in use by adb.c 0x00400000 */ diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index b2bc2516d6..5fb8c8653c 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -579,6 +579,11 @@ static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 }; static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data, ip6_arpa_offsets); +static unsigned char underscore_data[] = "\001_"; +static unsigned char underscore_offsets[] = { 0 }; +static const dns_name_t underscore_name = + DNS_NAME_INITNONABSOLUTE(underscore_data, underscore_offsets); + static void destroy(dns_resolver_t *res); static void empty_bucket(dns_resolver_t *res); static isc_result_t resquery_send(resquery_t *query); @@ -4055,6 +4060,14 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) { if (fctx->minimized && !fctx->forwarding) { unsigned int options = fctx->options; options &= ~DNS_FETCHOPT_QMINIMIZE; + /* + * In "_ A" mode we're asking for _.domain - + * resolver by default will follow delegations + * then, we don't want that. + */ + if ((options & DNS_FETCHOPT_QMIN_USE_A) != 0) { + options |= DNS_FETCHOPT_NOFOLLOW; + } fctx_increference(fctx); task = res->buckets[bucketnum].task; fctx_stoptimer(fctx); @@ -4157,8 +4170,19 @@ resume_qmin(isc_task_t *task, isc_event_t *event) { goto cleanup; } - if (NXDOMAIN_RESULT(result) || result == DNS_R_FORMERR || - result == DNS_R_REMOTEFORMERR) + /* + * If we're doing "_ A"-style minimization we can get + * NX answer to minimized query - we need to continue then. + * + * Otherwise - either disable minimization if we're + * in relaxed mode or fail if we're in strict mode. + */ + + if ((NXDOMAIN_RESULT(result) && + (fctx->options & DNS_FETCHOPT_QMIN_USE_A) == 0) || + result == DNS_R_FORMERR || + result == DNS_R_REMOTEFORMERR || + result == ISC_R_FAILURE) { if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) == 0) { fctx->qmin_labels = DNS_MAX_LABELS + 1; @@ -7607,7 +7631,10 @@ resquery_response(isc_task_t *task, isc_event_t *event) { case DNS_R_CHASEDSSERVERS: break; case DNS_R_DELEGATION: - result = ISC_R_SUCCESS; + /* With NOFOLLOW we want to pass the result code */ + if ((fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) { + result = ISC_R_SUCCESS; + } break; default: /* @@ -7641,10 +7668,11 @@ resquery_response(isc_task_t *task, isc_event_t *event) { * work to be queued to the DNSSEC validator. */ if (WANTCACHE(fctx)) { - result = cache_message(fctx, query->addrinfo, rctx.now); - if (result != ISC_R_SUCCESS) { - FCTXTRACE3("cache_message complete", result); - rctx_done(&rctx, result); + isc_result_t tresult; + tresult = cache_message(fctx, query->addrinfo, rctx.now); + if (tresult != ISC_R_SUCCESS) { + FCTXTRACE3("cache_message complete", tresult); + rctx_done(&rctx, tresult); return; } } @@ -9194,18 +9222,21 @@ rctx_referral(respctx_t *rctx) { * reset the fetch context counters. * */ - rctx->get_nameservers = true; - rctx->next_server = true; - rctx->fctx->restarts = 0; - rctx->fctx->referrals++; - rctx->fctx->querysent = 0; - rctx->fctx->lamecount = 0; - rctx->fctx->quotacount = 0; - rctx->fctx->neterr = 0; - rctx->fctx->badresp = 0; - rctx->fctx->adberr = 0; + if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) { + rctx->get_nameservers = true; + rctx->next_server = true; + rctx->fctx->restarts = 0; + rctx->fctx->referrals++; + rctx->fctx->querysent = 0; + rctx->fctx->lamecount = 0; + rctx->fctx->quotacount = 0; + rctx->fctx->neterr = 0; + rctx->fctx->badresp = 0; + rctx->fctx->adberr = 0; + } return (ISC_R_COMPLETE); + } /* @@ -10467,13 +10498,29 @@ fctx_minimize_qname(fetchctx_t *fctx) { * We want to query for qmin_labels from fctx->name */ dns_fixedname_t fname; - dns_fixedname_init(&fname); - dns_name_split(&fctx->name, - fctx->qmin_labels, + dns_name_t *name = dns_fixedname_initname(&fname); + dns_name_split(&fctx->name, fctx->qmin_labels, NULL, dns_fixedname_name(&fname)); - result = dns_name_dup(dns_fixedname_name(&fname), fctx->mctx, - &fctx->qminname); - fctx->qmintype = dns_rdatatype_ns; + if ((fctx->options & DNS_FETCHOPT_QMIN_USE_A) != 0) { + isc_buffer_t dbuf; + dns_fixedname_t tmpname; + dns_name_t *tname = dns_fixedname_initname(&tmpname); + char ndata[DNS_NAME_MAXWIRE]; + + isc_buffer_init(&dbuf, ndata, DNS_NAME_MAXWIRE); + dns_fixedname_init(&tmpname); + result = dns_name_concatenate(&underscore_name, + name, tname, &dbuf); + if (result == ISC_R_SUCCESS) { + result = dns_name_dup(tname, fctx->mctx, + &fctx->qminname); + } + fctx->qmintype = dns_rdatatype_a; + } else { + result = dns_name_dup(dns_fixedname_name(&fname), + fctx->mctx, &fctx->qminname); + fctx->qmintype = dns_rdatatype_ns; + } fctx->minimized = true; } else { /* Minimization is done, we'll ask for whole qname */ @@ -10482,6 +10529,14 @@ fctx_minimize_qname(fetchctx_t *fctx) { fctx->minimized = false; } + char domainbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&fctx->qminname, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(5), + "QNAME minimization - %s minimized, qmintype %d " + "qminname %s", fctx->minimized ? "" : "not", + fctx->qmintype, domainbuf); + return (result); } diff --git a/lib/ns/query.c b/lib/ns/query.c index a51bffe5fc..06f2b2fb27 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -11088,6 +11088,8 @@ ns_query_start(ns_client_t *client) { DNS_FETCHOPT_QMIN_SKIP_IP6A; if (client->view->qmin_strict) { client->query.fetchoptions |= DNS_FETCHOPT_QMIN_STRICT; + } else { + client->query.fetchoptions |= DNS_FETCHOPT_QMIN_USE_A; } }