Commit c85b467d authored by Mark Andrews's avatar Mark Andrews
Browse files

4747. [func] Synthesis of responses from DNSSEC-verified records.

                        Stage 3 - synthesize NODATA responses. [RT #40138]
parent af3f476e
4747. [func] Synthesis of responses from DNSSEC-verified records.
Stage 3 - synthesize NODATA responses. [RT #40138]
4746. [cleanup] Add configured prefixes to configure summary
output. [RT #46153]
......
......@@ -92,8 +92,8 @@ grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1
grep "example.*3600.IN.SOA" dig.out.ns2.test$n > /dev/null && ret=1
$PERL ../digcomp.pl $nodata dig.out.ns2.test$n || ret=1
n=`expr $n + 1`
if [ $ret != 0 ]; then echo "I:failed (ignored - to be supported in the future)"; fi
: status=`expr $status + $ret`
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:check synthesized wildcard response ($n)"
ret=0
......
......@@ -7229,6 +7229,12 @@ options {
DNSSEC validation must be enabled for this
option to be effective.
</para>
<para>
This initial implementation only covers synthesis
of answers from NSEC records. Synthesis from NSEC3
is planned for the future. This will also be
controlled by <command>synth-from-dnssec</command>.
</para>
</listitem>
</itemizedlist>
</para>
......
......@@ -166,13 +166,17 @@
</listitem>
<listitem>
<para>
<command>named</command> can now synthesize NXDOMAIN responses
from cached DNSSEC-verified records returned in negative or
wildcard responses. This will reduce query loads on
authoritative servers for signed domains: if existing cached
records can be used by the resolver to determine that a name does
not exist in the authorittive domain, then no query needs to
be sent.
<command>named</command> can now synthesize negative responses
(NXDOMAIN, NODATA, or wildcard answers) from cached DNSSEC-verified
records that were returned in negative or wildcard responses from
authoritative servers.
</para>
<para>
This will reduce query loads on authoritative servers for signed
domains: when existing cached records can be used by the resolver
to determine that a name does not exist in the authorittive domain,
no query needs to be sent. Reducing the number of iterative queries
should also improve resolver performance.
</para>
<para>
This behavior is controlled by the new
......@@ -180,6 +184,11 @@
<command>synth-from-dnssec</command>. It is enabled by
default.
</para>
<para>
Note: this currently only works for zones signed using NSEC.
Support for zones signed using NSEC3 (without opt-out) is
planned for the future.
</para>
<para>
Thanks to APNIC for sponsoring this work.
</para>
......
......@@ -5027,6 +5027,7 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
rdatasetheader_t *found, *nsheader;
rdatasetheader_t *foundsig, *nssig, *cnamesig;
rdatasetheader_t *update, *updatesig;
rdatasetheader_t *nsecheader, *nsecsig;
rbtdb_rdatatype_t sigtype, negtype;
UNUSED(version);
......@@ -5108,7 +5109,9 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
negtype = RBTDB_RDATATYPE_VALUE(0, type);
nsheader = NULL;
nsecheader = NULL;
nssig = NULL;
nsecsig = NULL;
cnamesig = NULL;
empty_node = ISC_TRUE;
header_prev = NULL;
......@@ -5172,6 +5175,10 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
* need its signature.
*/
nssig = header;
} else if (header->type == dns_rdatatype_nsec) {
nsecheader = header;
} else if (header->type == RBTDB_RDATATYPE_SIGNSEC) {
nsecsig = header;
} else if (cname_ok &&
header->type == RBTDB_RDATATYPE_SIGCNAME) {
/*
......@@ -5205,6 +5212,32 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
((options & DNS_DBFIND_GLUEOK) == 0)) ||
(DNS_TRUST_PENDING(found->trust) &&
((options & DNS_DBFIND_PENDINGOK) == 0))) {
/*
* Return covering NODATA NSEC record.
*/
if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
nsecheader != NULL)
{
if (nodep != NULL) {
new_reference(search.rbtdb, node);
INSIST(!ISC_LINK_LINKED(node, deadlink));
*nodep = node;
}
bind_rdataset(search.rbtdb, node, nsecheader,
search.now, rdataset);
if (need_headerupdate(nsecheader, search.now))
update = nsecheader;
if (nsecsig != NULL) {
bind_rdataset(search.rbtdb, node, nsecsig,
search.now, sigrdataset);
if (need_headerupdate(nsecsig, search.now))
updatesig = nsecsig;
}
result = DNS_R_COVERINGNSEC;
goto node_exit;
}
/*
* If there is an NS rdataset at this node, then this is the
* deepest zone cut.
......
......@@ -8341,6 +8341,87 @@ log_noexistnodata(void *val, int level, const char *fmt, ...) {
va_end(ap);
}
/*
* Synthesize a NODATA response from the SOA and covering NSEC in cache.
*/
static isc_result_t
query_synthnodata(query_ctx_t *qctx, const dns_name_t *signer,
dns_rdataset_t **soardatasetp,
dns_rdataset_t **sigsoardatasetp)
{
dns_name_t *name = NULL;
dns_ttl_t ttl;
isc_buffer_t *dbuf, b;
isc_result_t result;
dns_rdataset_t *clone = NULL, *sigclone = NULL;
/*
* Detemine the correct TTL to use for the SOA and RRSIG
*/
ttl = ISC_MIN(qctx->rdataset->ttl, qctx->sigrdataset->ttl);
ttl = ISC_MIN(ttl, (*soardatasetp)->ttl);
ttl = ISC_MIN(ttl, (*sigsoardatasetp)->ttl);
(*soardatasetp)->ttl = (*sigsoardatasetp)->ttl = ttl;
/*
* We want the SOA record to be first, so save the
* NODATA proof's name now or else discard it.
*/
if (WANTDNSSEC(qctx->client)) {
query_keepname(qctx->client, qctx->fname, qctx->dbuf);
} else {
query_releasename(qctx->client, &qctx->fname);
}
dbuf = query_getnamebuf(qctx->client);
if (dbuf == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
name = query_newname(qctx->client, dbuf, &b);
if (name == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
dns_name_clone(signer, name);
/*
* Add SOA record. Omit the RRSIG if DNSSEC was not requested.
*/
if (!WANTDNSSEC(qctx->client)) {
sigsoardatasetp = NULL;
}
query_addrrset(qctx->client, &name, soardatasetp, sigsoardatasetp,
dbuf, DNS_SECTION_AUTHORITY);
if (WANTDNSSEC(qctx->client)) {
/*
* Add NODATA proof.
*/
query_addrrset(qctx->client, &qctx->fname,
&qctx->rdataset, &qctx->sigrdataset,
NULL, DNS_SECTION_AUTHORITY);
}
result = ISC_R_SUCCESS;
inc_stats(qctx->client, ns_statscounter_nodatasynth);
cleanup:
if (name != NULL) {
query_releasename(qctx->client, &name);
}
if (clone != NULL) {
query_putrdataset(qctx->client, &clone);
}
if (sigclone != NULL) {
query_putrdataset(qctx->client, &sigclone);
}
return (result);
}
/*
* Synthesize a wildcard answer using the contents of 'rdataset'.
* qctx contains the NODATA proof.
......@@ -8405,7 +8486,7 @@ query_synthwildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset,
if (WANTDNSSEC(qctx->client)) {
/*
* Add NODATA proof.
* Add NOQNAME proof.
*/
query_addrrset(qctx->client, &qctx->fname,
&qctx->rdataset, &qctx->sigrdataset,
......@@ -8429,7 +8510,7 @@ cleanup:
}
/*
* Add a synthesised CNAME record from the wildard RRset (rdataset)
* Add a synthesized CNAME record from the wildard RRset (rdataset)
* and NODATA proof by calling query_synthwildcard then setup to
* follow the CNAME.
*/
......@@ -8487,7 +8568,7 @@ query_synthcnamewildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset,
}
/*
* Synthesise a NXDOMAIN response from qctx (which contains the
* Synthesize a NXDOMAIN response from qctx (which contains the
* NODATA proof), nowild + rdataset + sigrdataset (which contains
* the NOWILDCARD proof) and signer + soardatasetp + sigsoardatasetp
* which contain the SOA record + RRSIG for the negative answer.
......@@ -8553,7 +8634,7 @@ query_synthnxdomain(query_ctx_t *qctx,
if (WANTDNSSEC(qctx->client)) {
/*
* Add NODATA proof.
* Add NOQNAME proof.
*/
query_addrrset(qctx->client, &qctx->fname,
&qctx->rdataset, &qctx->sigrdataset,
......@@ -8607,22 +8688,51 @@ cleanup:
return (result);
}
/*
* Check that all signer names in sigrdataset match the expected signer.
*/
static isc_result_t
checksignames(dns_name_t *signer, dns_rdataset_t *sigrdataset) {
isc_result_t result;
for (result = dns_rdataset_first(sigrdataset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(sigrdataset)) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_rrsig_t rrsig;
dns_rdataset_current(sigrdataset, &rdata);
result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (dns_name_countlabels(signer) == 0) {
dns_name_copy(&rrsig.signer, signer, NULL);
} else if (!dns_name_equal(signer, &rrsig.signer)) {
return (ISC_R_FAILURE);
}
}
return (ISC_R_SUCCESS);
}
/*%
* Handle covering NSEC responses.
*
* Verify the NSEC record is apropriate for the QNAME, if not
* Verify the NSEC record is apropriate for the QNAME; if not,
* redo the initial query without DNS_DBFIND_COVERINGNSEC.
*
* Compute the wildcard record and check if the wildcard name
* exists or not. If we can't determine this redo the initial
* query without DNS_DBFIND_COVERINGNSEC.
* If the covering NSEC proves that the name exists but not the type,
* synthesize a NODATA response.
*
* If the name doesn't exist, compute the wildcard record and check whether
* the wildcard name exists or not. If we can't determine this, redo the
* initial query without DNS_DBFIND_COVERINGNSEC.
*
* If the wildcard name does not exist compute the SOA name and look
* that up. If the SOA record does not exist redo the initial query
* without DNS_DBFIND_COVERINGNSEC. If the SOA record exists constructed
* a NXDOMAIN response from the found records.
* If the wildcard name does not exist, compute the SOA name and look that
* up. If the SOA record does not exist, redo the initial query without
* DNS_DBFIND_COVERINGNSEC. If the SOA record exists, synthesize an
* NXDOMAIN response from the found records.
*
* If the wildcard name does exist perform a lookup for the requested
* If the wildcard name does exist, perform a lookup for the requested
* type at the wildcard name.
*/
static isc_result_t
......@@ -8673,21 +8783,10 @@ query_coveringnsec(query_ctx_t *qctx) {
/*
* All signer names must be the same to accept.
*/
for (result = dns_rdataset_first(qctx->sigrdataset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(qctx->sigrdataset))
{
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_rrsig_t rrsig;
dns_rdataset_current(qctx->sigrdataset, &rdata);
result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (dns_name_countlabels(signer) == 0) {
dns_name_copy(&rrsig.signer, signer, NULL);
} else if (!dns_name_equal(signer, &rrsig.signer)) {
goto cleanup;
}
result = checksignames(signer, qctx->sigrdataset);
if (result != ISC_R_SUCCESS) {
result = ISC_R_SUCCESS;
goto cleanup;
}
/*
......@@ -8698,7 +8797,56 @@ query_coveringnsec(query_ctx_t *qctx) {
&exists, &data, wild,
log_noexistnodata, qctx);
if (result != ISC_R_SUCCESS || exists) {
if (result != ISC_R_SUCCESS || (exists && data)) {
goto cleanup;
}
if (exists) {
if (qctx->type == dns_rdatatype_any) { /* XXX not yet */
goto cleanup;
}
#ifdef ALLOW_FILTER_AAAA
if (qctx->client->filter_aaaa != dns_aaaa_ok &&
(qctx->type == dns_rdatatype_a ||
qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */
{
goto cleanup;
}
#endif
if (!ISC_LIST_EMPTY(qctx->client->view->dns64) &&
(qctx->type == dns_rdatatype_a ||
qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */
{
goto cleanup;
}
if (!qctx->resuming && !STALE(qctx->rdataset) &&
qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client))
{
goto cleanup;
}
soardataset = query_newrdataset(qctx->client);
sigsoardataset = query_newrdataset(qctx->client);
if (soardataset == NULL || sigsoardataset == NULL) {
goto cleanup;
}
/*
* Look for SOA record to construct NODATA response.
*/
dns_db_attach(qctx->db, &db);
result = dns_db_findext(db, signer, qctx->version,
dns_rdatatype_soa, dboptions,
qctx->client->now, &node,
fname, &cm, &ci, soardataset,
sigsoardataset);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
(void)query_synthnodata(qctx, signer,
&soardataset, &sigsoardataset);
done = ISC_TRUE;
goto cleanup;
}
......@@ -8768,8 +8916,7 @@ query_coveringnsec(query_ctx_t *qctx) {
done = ISC_TRUE;
goto cleanup;
case DNS_R_CNAME: /* wild card cname */
(void)query_synthcnamewildcard(qctx, &rdataset,
&sigrdataset);
(void)query_synthcnamewildcard(qctx, &rdataset, &sigrdataset);
done = ISC_TRUE;
goto cleanup;
case DNS_R_NCACHENXRRSET: /* wild card nodata */
......@@ -8789,26 +8936,19 @@ query_coveringnsec(query_ctx_t *qctx) {
}
/*
* All signer names must be the same to accept.
* Must be signed to accept.
*/
if (!dns_rdataset_isassociated(&sigrdataset)) {
goto cleanup;
}
for (result = dns_rdataset_first(&sigrdataset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&sigrdataset)) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_rrsig_t rrsig;
dns_rdataset_current(&sigrdataset, &rdata);
result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (dns_name_countlabels(signer) == 0) {
dns_name_copy(&rrsig.signer, signer, NULL);
} else if (!dns_name_equal(signer, &rrsig.signer)) {
goto cleanup;
}
/*
* Check signer signer names again.
*/
result = checksignames(signer, &sigrdataset);
if (result != ISC_R_SUCCESS) {
result = ISC_R_SUCCESS;
goto cleanup;
}
if (node != NULL) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment