Use experimental "_ A" minimization in relaxed mode.

qname minimization, even in relaxed mode, can fail on
some very broken domains. In relaxed mode, instead of
asking for "foo.bar NS" ask for "_.foo.bar A" to either
get a delegation or NXDOMAIN. It will require more queries
than regular mode for proper NXDOMAINs.
This commit is contained in:
Witold Kręcicki 2019-05-28 14:03:13 +02:00 committed by Evan Hunt
parent 2691e729f0
commit ae52c2117e
6 changed files with 136 additions and 35 deletions

View file

@ -21,7 +21,7 @@ else
TESTSOCK6=false
fi
TESTSOCK6="$TESTSOCK6"
export LANG=C
. ${TOP}/version

View file

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

View file

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

View file

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

View file

@ -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 || result == ISC_R_FAILURE)
/*
* 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);
}
/*
@ -10471,9 +10502,28 @@ fctx_minimize_qname(fetchctx_t *fctx) {
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;
char ndata[DNS_NAME_MAXWIRE];
isc_buffer_init(&dbuf, ndata, DNS_NAME_MAXWIRE);
dns_fixedname_init(&tmpname);
result = dns_name_concatenate(&underscore_name,
dns_fixedname_name(&fname),
dns_fixedname_name(&tmpname),
&dbuf);
if (result == ISC_R_SUCCESS) {
result = dns_name_dup(dns_fixedname_name(&tmpname),
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 +10532,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);
}

View file

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