mirror of
https://github.com/isc-projects/bind9.git
synced 2026-04-29 18:09:11 -04:00
4747. [func] Synthesis of responses from DNSSEC-verified records.
Stage 3 - synthesize NODATA responses. [RT #40138]
This commit is contained in:
parent
af3f476e77
commit
c85b467dc0
6 changed files with 246 additions and 55 deletions
3
CHANGES
3
CHANGES
|
|
@ -1,3 +1,6 @@
|
|||
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.
|
||||
|
|
|
|||
232
lib/ns/query.c
232
lib/ns/query.c
|
|
@ -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 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 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 exist perform a lookup for the requested
|
||||
* 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
|
||||
* 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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue