From 0aed466565f9d8052af1c9dadad3e35527201c62 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 31 Aug 2017 07:57:50 +1000 Subject: [PATCH] 4693. [func] Synthesis of responses from DNSSEC-verified records. Stage 1 covers NXDOMAIN synthesis from NSEC records. This is controlled by synth-from-dnssec and is enabled by default. [RT #40138] --- CHANGES | 5 + bin/named/config.c | 93 ++--- bin/named/include/named/query.h | 1 - bin/named/include/named/server.h | 6 +- bin/named/named.conf.docbook | 2 + bin/named/query.c | 379 +++++++++++++++++- bin/named/server.c | 5 + bin/named/statschannel.c | 3 + bin/tests/system/checkconf/good.conf | 2 +- bin/tests/system/conf.sh.in | 5 +- bin/tests/system/dnssec/tests.sh | 8 +- bin/tests/system/synthfromdnssec/clean.sh | 10 + .../system/synthfromdnssec/ns1/example.db.in | 7 + .../system/synthfromdnssec/ns1/named.conf | 37 ++ .../system/synthfromdnssec/ns1/root.db.in | 6 + bin/tests/system/synthfromdnssec/ns1/sign.sh | 40 ++ .../system/synthfromdnssec/ns2/named.conf | 33 ++ .../system/synthfromdnssec/ns2/root.hints | 2 + .../system/synthfromdnssec/ns3/named.conf | 37 ++ .../system/synthfromdnssec/ns3/redirect.db | 17 + .../system/synthfromdnssec/ns3/root.hints | 2 + bin/tests/system/synthfromdnssec/setup.sh | 17 + bin/tests/system/synthfromdnssec/tests.sh | 139 +++++++ doc/arm/Bv9ARM-book.xml | 36 +- doc/arm/notes.xml | 15 + doc/misc/options | 22 +- lib/dns/include/dns/view.h | 1 + lib/dns/message.c | 19 +- lib/dns/rbtdb.c | 14 +- lib/dns/resolver.c | 44 +- lib/dns/view.c | 1 + lib/isccfg/namedconf.c | 17 +- 32 files changed, 927 insertions(+), 98 deletions(-) create mode 100644 bin/tests/system/synthfromdnssec/clean.sh create mode 100644 bin/tests/system/synthfromdnssec/ns1/example.db.in create mode 100644 bin/tests/system/synthfromdnssec/ns1/named.conf create mode 100644 bin/tests/system/synthfromdnssec/ns1/root.db.in create mode 100644 bin/tests/system/synthfromdnssec/ns1/sign.sh create mode 100644 bin/tests/system/synthfromdnssec/ns2/named.conf create mode 100644 bin/tests/system/synthfromdnssec/ns2/root.hints create mode 100644 bin/tests/system/synthfromdnssec/ns3/named.conf create mode 100644 bin/tests/system/synthfromdnssec/ns3/redirect.db create mode 100644 bin/tests/system/synthfromdnssec/ns3/root.hints create mode 100644 bin/tests/system/synthfromdnssec/setup.sh create mode 100644 bin/tests/system/synthfromdnssec/tests.sh diff --git a/CHANGES b/CHANGES index b52f3a06ac..ca681c5d2b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +4693. [func] Synthesis of responses from DNSSEC-verified records. + Stage 1 covers NXDOMAIN synthesis from NSEC records. + This is controlled by synth-from-dnssec and is enabled + by default. [RT #40138] + 4692. [bug] Fix build failures with libressl introduced in 4676. [RT #45879] diff --git a/bin/named/config.c b/bin/named/config.c index 743cfc7dd3..606e083134 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -128,55 +128,40 @@ options {\n\ dnssec-lookaside . trust-anchor dlv.isc.org;\n\ \n\ /* view */\n\ + allow-new-zones no;\n\ allow-notify {none;};\n\ - allow-update-forwarding {none;};\n\ allow-query-cache { localnets; localhost; };\n\ allow-query-cache-on { any; };\n\ allow-recursion { localnets; localhost; };\n\ allow-recursion-on { any; };\n\ + allow-update-forwarding {none;};\n\ # allow-v6-synthesis ;\n\ -# sortlist \n\ -# topology \n\ auth-nxdomain false;\n\ - glue-cache yes;\n\ - minimal-any false;\n\ - minimal-responses true;\n\ - recursion true;\n\ - provide-ixfr true;\n\ - request-ixfr true;\n\ - request-expire true;\n\ -# fetch-glue ;\n\ -# rfc2308-type1 ;\n\ - query-source address *;\n\ - query-source-v6 address *;\n\ - notify-source *;\n\ - notify-source-v6 *;\n\ - cleaning-interval 0; /* now meaningless */\n\ -# min-roots ;\n\ - lame-ttl 600;\n\ - servfail-ttl 1;\n\ - max-ncache-ttl 10800; /* 3 hours */\n\ - max-cache-ttl 604800; /* 1 week */\n\ - transfer-format many-answers;\n\ - max-cache-size 90%;\n\ - check-names master fail;\n\ - check-names slave warn;\n\ - check-names response ignore;\n\ check-dup-records warn;\n\ check-mx warn;\n\ + check-names master fail;\n\ + check-names response ignore;\n\ + check-names slave warn;\n\ check-spf warn;\n\ + cleaning-interval 0; /* now meaningless */\n\ + clients-per-query 10;\n\ + dnssec-accept-expired no;\n\ dnssec-enable yes;\n\ dnssec-validation yes; \n\ - dnssec-accept-expired no;\n\ - fetches-per-zone 0;\n\ +# fetch-glue ;\n\ fetch-quota-params 100 0.1 0.3 0.7;\n\ - clients-per-query 10;\n\ - max-clients-per-query 100;\n\ - max-recursion-depth 7;\n\ - max-recursion-queries 75;\n\ - zero-no-soa-ttl-cache no;\n\ - nsec3-test-zone no;\n\ - allow-new-zones no;\n\ + fetches-per-server 0;\n\ + fetches-per-zone 0;\n\ +" +#ifdef ALLOW_FILTER_AAAA +" filter-aaaa-on-v4 no;\n\ + filter-aaaa-on-v6 no;\n\ + filter-aaaa { any; };\n\ +" +#endif +"\ + glue-cache yes;\n\ + lame-ttl 600;\n\ " #ifdef HAVE_LMDB "\ @@ -184,10 +169,34 @@ options {\n\ " #endif "\ - fetches-per-server 0;\n\ - require-server-cookie no;\n\ - v6-bias 50;\n\ + max-cache-size 90%;\n\ + max-cache-ttl 604800; /* 1 week */\n\ + max-clients-per-query 100;\n\ + max-ncache-ttl 10800; /* 3 hours */\n\ + max-recursion-depth 7;\n\ + max-recursion-queries 75;\n\ message-compression yes;\n\ +# min-roots ;\n\ + minimal-any false;\n\ + minimal-responses true;\n\ + notify-source *;\n\ + notify-source-v6 *;\n\ + nsec3-test-zone no;\n\ + provide-ixfr true;\n\ + query-source address *;\n\ + query-source-v6 address *;\n\ + recursion true;\n\ + request-expire true;\n\ + request-ixfr true;\n\ + require-server-cookie no;\n\ +# rfc2308-type1 ;\n\ + servfail-ttl 1;\n\ +# sortlist \n\ + synth-from-dnssec yes;\n\ +# topology \n\ + transfer-format many-answers;\n\ + v6-bias 50;\n\ + zero-no-soa-ttl-cache no;\n\ " #ifdef HAVE_DNSTAP "\ @@ -199,12 +208,6 @@ options {\n\ geoip-use-ecs yes;\n\ " #endif -#ifdef ALLOW_FILTER_AAAA -" filter-aaaa-on-v4 no;\n\ - filter-aaaa-on-v6 no;\n\ - filter-aaaa { any; };\n\ -" -#endif " /* zone */\n\ allow-query {any;};\n\ diff --git a/bin/named/include/named/query.h b/bin/named/include/named/query.h index 8f20c9af02..ad252e8b3c 100644 --- a/bin/named/include/named/query.h +++ b/bin/named/include/named/query.h @@ -71,7 +71,6 @@ struct ns_query { isc_boolean_t authoritative; isc_boolean_t is_zone; } redirect; - }; #define NS_QUERYATTR_RECURSIONOK 0x0001 diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 355c64d53d..d45cc8a25a 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -205,7 +205,11 @@ enum { dns_nsstatscounter_cookienew = 56, dns_nsstatscounter_badcookie = 57, - dns_nsstatscounter_max = 58 + dns_nsstatscounter_nxdomainsynth = 58, + dns_nsstatscounter_nodatasynth = 59, + dns_nsstatscounter_wildcardsynth = 60, + + dns_nsstatscounter_max = 61 }; /*% diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index 5541179417..0eb9725c9b 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -435,6 +435,7 @@ options { stacksize ( default | unlimited | sizeval ); startup-notify-rate integer; statistics-file quoted_string; + synth-from-dnssec boolean; tcp-advertised-timeout integer; tcp-clients integer; tcp-idle-timeout integer; @@ -769,6 +770,7 @@ view string classinteger; sig-validity-interval integer integer ; sortlist { address_match_element; ... }; + synth-from-dnssec boolean; transfer-format ( many-answers | one-answer ); transfer-source ( ipv4_address | * ) port ( integer | * ) dscp integer ; diff --git a/bin/named/query.c b/bin/named/query.c index 5b2b7a8f09..96262b2a2a 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -231,6 +232,10 @@ static isc_boolean_t rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); +static void +log_noexistnodata(void *val, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + /*% * The structure and functions defined below implement the query logic * that previously lived in the single very complex function query_find(). @@ -320,6 +325,7 @@ typedef struct query_ctx { * restart needed */ isc_boolean_t need_wildcardproof; /* wilcard proof needed */ isc_boolean_t nxrewrite; /* negative answer from RPZ */ + isc_boolean_t findcoveringnsec; /* lookup covering NSEC */ dns_fixedname_t wildcardname; /* name needing wcard proof */ dns_fixedname_t dsname; /* name needing DS */ @@ -430,6 +436,9 @@ query_redirect(query_ctx_t *qctx); static isc_result_t query_ncache(query_ctx_t *qctx, isc_result_t result); +static isc_result_t +query_coveringnsec(query_ctx_t *qctx); + static isc_result_t query_cname(query_ctx_t *qctx); @@ -4268,7 +4277,8 @@ dns64_aaaaok(ns_client_t *client, dns_rdataset_t *rdataset, if (RECURSIONOK(client)) flags |= DNS_DNS64_RECURSIVE; - if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) + if (WANTDNSSEC(client) && sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) flags |= DNS_DNS64_DNSSEC; count = dns_rdataset_count(rdataset); @@ -4621,6 +4631,7 @@ qctx_init(ns_client_t *client, dns_fetchevent_t *event, qctx->options = 0; qctx->resuming = ISC_FALSE; qctx->is_zone = ISC_FALSE; + qctx->findcoveringnsec = client->view->synthfromdnssec; qctx->is_staticstub_zone = ISC_FALSE; qctx->nxrewrite = ISC_FALSE; qctx->authoritative = ISC_FALSE; @@ -4920,6 +4931,7 @@ query_lookup(query_ctx_t *qctx) { dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_name_t *rpzqname = NULL; + unsigned int dboptions; CCTRACE(ISC_LOG_DEBUG(3), "query_lookup"); @@ -4947,7 +4959,7 @@ query_lookup(query_ctx_t *qctx) { return (query_done(qctx)); } - if (WANTDNSSEC(qctx->client) && + if ((WANTDNSSEC(qctx->client) || qctx->findcoveringnsec) && (!qctx->is_zone || dns_db_issecure(qctx->db))) { qctx->sigrdataset = query_newrdataset(qctx->client); @@ -4968,10 +4980,12 @@ query_lookup(query_ctx_t *qctx) { rpzqname = qctx->client->query.qname; } - result = dns_db_findext(qctx->db, rpzqname, - qctx->version, qctx->type, - qctx->client->query.dboptions, - qctx->client->now, &qctx->node, + dboptions = qctx->client->query.dboptions; + if (!qctx->is_zone && qctx->findcoveringnsec) + dboptions |= DNS_DBFIND_COVERINGNSEC; + + result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type, + dboptions, qctx->client->now, &qctx->node, qctx->fname, &cm, &ci, qctx->rdataset, qctx->sigrdataset); @@ -5955,6 +5969,9 @@ query_gotanswer(query_ctx_t *qctx, isc_result_t result) { case DNS_R_NXDOMAIN: return (query_nxdomain(qctx, ISC_FALSE)); + case DNS_R_COVERINGNSEC: + return (query_coveringnsec(qctx)); + case DNS_R_NCACHENXDOMAIN: result = query_redirect(qctx); if (result != ISC_R_COMPLETE) @@ -6507,7 +6524,7 @@ query_respond(query_ctx_t *qctx) { return (query_lookup(qctx)); } - if (qctx->sigrdataset != NULL) { + if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) { sigrdatasetp = &qctx->sigrdataset; } @@ -6688,7 +6705,7 @@ query_dns64(query_ctx_t *qctx) { * We use the signatures from the A lookup to set DNS_DNS64_DNSSEC * as this provides a easy way to see if the answer was signed. */ - if (qctx->sigrdataset != NULL && + if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL && dns_rdataset_isassociated(qctx->sigrdataset)) flags |= DNS_DNS64_DNSSEC; @@ -7098,7 +7115,7 @@ query_zone_delegation(query_ctx_t *qctx) { */ qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; - if (qctx->sigrdataset != NULL) + if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) sigrdatasetp = &qctx->sigrdataset; query_addrrset(qctx->client, &qctx->fname, &qctx->rdataset, sigrdatasetp, @@ -7262,7 +7279,7 @@ query_delegation(query_ctx_t *qctx) { * delegations. */ qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL; - if (qctx->sigrdataset != NULL) { + if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) { sigrdatasetp = &qctx->sigrdataset; } query_addrrset(qctx->client, &qctx->fname, @@ -7869,6 +7886,344 @@ query_redirect(query_ctx_t *qctx) { return (ISC_R_COMPLETE); } +/*% + * Logging function to be passed to dns_nsec_noexistnodata. + */ +static void +log_noexistnodata(void *val, int level, const char *fmt, ...) { + query_ctx_t *qctx = val; + va_list ap; + + va_start(ap, fmt); + ns_client_logv(qctx->client, NS_LOGCATEGORY_QUERIES, + NS_LOGMODULE_QUERY, level, fmt, ap); + va_end(ap); +} + +/*% + * Handle covering NSEC responses. + * + * 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 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 exist perform a lookup for the requested + * type at the wildcard name. + */ +static isc_result_t +query_coveringnsec(query_ctx_t *qctx) { + dns_db_t *db = NULL; + dns_clientinfo_t ci; + dns_clientinfomethods_t cm; + dns_dbnode_t *node = NULL; + dns_fixedname_t fixed; + dns_fixedname_t fnowild; + dns_fixedname_t fsigner; + dns_fixedname_t fwild; + dns_name_t *fname = NULL; + dns_name_t *name = NULL; + dns_name_t *nowild = NULL; + dns_name_t *signer = NULL; + dns_name_t *wild = NULL; + dns_rdataset_t **sigsoardatasetp = NULL; + dns_rdataset_t *clone = NULL, *sigclone = NULL; + dns_rdataset_t *soardataset = NULL, *sigsoardataset = NULL; + dns_rdataset_t rdataset, sigrdataset; + dns_ttl_t ttl; + isc_boolean_t done = ISC_FALSE; + isc_boolean_t exists = ISC_TRUE, data = ISC_TRUE; + isc_boolean_t redirected = ISC_FALSE; + isc_buffer_t *dbuf = NULL, b; + isc_result_t result; + unsigned int dboptions = qctx->client->query.dboptions; + + /* + * If we have no signer name, stop immediately. + */ + if (!dns_rdataset_isassociated(qctx->sigrdataset)) { + goto cleanup; + } + + dns_fixedname_init(&fwild); + wild = dns_fixedname_name(&fwild); + dns_fixedname_init(&fixed); + fname = dns_fixedname_name(&fixed); + dns_fixedname_init(&fsigner); + signer = dns_fixedname_name(&fsigner); + dns_fixedname_init(&fnowild); + nowild = dns_fixedname_name(&fnowild); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&sigrdataset); + + dns_clientinfomethods_init(&cm, ns_client_sourceip); + dns_clientinfo_init(&ci, qctx->client, NULL); + + /* + * 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; + } + } + + /* + * Check that we have the correct NOQNAME NSEC record. + */ + result = dns_nsec_noexistnodata(qctx->qtype, qctx->client->query.qname, + qctx->fname, qctx->rdataset, + &exists, &data, wild, + log_noexistnodata, qctx); + + if (result != ISC_R_SUCCESS || exists) { + goto cleanup; + } + + /* + * Look up the no-wildcard proof. + */ + dns_db_attach(qctx->db, &db); + result = dns_db_findext(db, wild, qctx->version, qctx->type, + dboptions | DNS_DBFIND_COVERINGNSEC, + qctx->client->now, &node, nowild, + &cm, &ci, &rdataset, &sigrdataset); + + if (rdataset.trust != dns_trust_secure || + sigrdataset.trust != dns_trust_secure) + { + goto cleanup; + } + + switch (result) { + case DNS_R_COVERINGNSEC: + result = dns_nsec_noexistnodata(qctx->qtype, wild, + nowild, &rdataset, + &exists, &data, NULL, + log_noexistnodata, qctx); + if (result != ISC_R_SUCCESS || exists) + goto cleanup; + break; + case ISC_R_SUCCESS: /* wild card match */ + case DNS_R_CNAME: /* wild card cname */ + case DNS_R_NCACHENXRRSET: /* wild card nodata */ + case DNS_R_NCACHENXDOMAIN: /* direct nxdomain */ + default: + goto cleanup; + } + + /* + * We now have the proof that we have an NXDOMAIN. Apply + * NXDOMAIN redirection if configured. + */ + result = query_redirect(qctx); + if (result != ISC_R_COMPLETE) { + redirected = ISC_TRUE; + goto cleanup; + } + + /* + * All signer names must be the same 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; + } + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + + soardataset = query_newrdataset(qctx->client); + sigsoardataset = query_newrdataset(qctx->client); + if (soardataset == NULL || sigsoardataset == NULL) { + goto cleanup; + } + + /* + * Look for SOA record to construct NXDOMAIN response. + */ + 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; + } + + qctx->client->message->rcode = dns_rcode_nxdomain; + + /* + * Detemine the correct TTL to use for the SOA and RRSIG + */ + ttl = ISC_MIN(qctx->rdataset->ttl, qctx->sigrdataset->ttl); + ttl = ISC_MIN(ttl, rdataset.ttl); + ttl = ISC_MIN(ttl, sigrdataset.ttl); + ttl = ISC_MIN(ttl, soardataset->ttl); + ttl = ISC_MIN(ttl, sigsoardataset->ttl); + + soardataset->ttl = sigsoardataset->ttl = ttl; + + /* + * We want the SOA record to be first, so save the + * NOQNAME 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) { + goto cleanup; + } + + name = query_newname(qctx->client, dbuf, &b); + if (name == NULL) { + goto cleanup; + } + + dns_name_clone(signer, name); + + /* + * Add SOA record. Omit the RRSIG if DNSSEC was not requested. + */ + if (WANTDNSSEC(qctx->client)) { + sigsoardatasetp = &sigsoardataset; + } + query_addrrset(qctx->client, &name, &soardataset, 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); + + dbuf = query_getnamebuf(qctx->client); + if (dbuf == NULL) { + goto cleanup; + } + + name = query_newname(qctx->client, dbuf, &b); + if (name == NULL) { + goto cleanup; + } + + dns_name_clone(nowild, name); + + clone = query_newrdataset(qctx->client); + sigclone = query_newrdataset(qctx->client); + if (clone == NULL || sigclone == NULL) { + goto cleanup; + } + + dns_rdataset_clone(&rdataset, clone); + dns_rdataset_clone(&sigrdataset, sigclone); + + /* + * Add NOWILDCARD proof. + */ + query_addrrset(qctx->client, &name, &clone, &sigclone, + dbuf, DNS_SECTION_AUTHORITY); + } + + inc_stats(qctx->client, dns_nsstatscounter_nxdomainsynth); + + done = ISC_TRUE; + + cleanup: + if (clone != NULL) { + query_putrdataset(qctx->client, &clone); + } + if (sigclone != NULL) { + query_putrdataset(qctx->client, &sigclone); + } + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (dns_rdataset_isassociated(&sigrdataset)) { + dns_rdataset_disassociate(&sigrdataset); + } + if (soardataset != NULL) { + query_putrdataset(qctx->client, &soardataset); + } + if (sigsoardataset != NULL) { + query_putrdataset(qctx->client, &sigsoardataset); + } + if (db != NULL) { + if (node != NULL) { + dns_db_detachnode(db, &node); + } + dns_db_detach(&db); + } + if (name != NULL) { + query_releasename(qctx->client, &name); + } + + if (redirected) { + return (result); + } + + if (!done) { + /* + * No covering NSEC was found; proceed with recursion. + */ + qctx->findcoveringnsec = ISC_FALSE; + if (qctx->fname != NULL) { + query_releasename(qctx->client, &qctx->fname); + } + if (qctx->node != NULL) { + dns_db_detachnode(qctx->db, &qctx->node); + } + query_putrdataset(qctx->client, &qctx->rdataset); + if (qctx->sigrdataset != NULL) { + query_putrdataset(qctx->client, &qctx->sigrdataset); + } + return (query_lookup(qctx)); + } + + return (query_done(qctx)); +} + /*% * Handle negative cache responses, DNS_R_NCACHENXRRSET or * DNS_R_NCACHENXDOMAIN. (Note: may also be called with result @@ -7955,7 +8310,7 @@ query_cname(query_ctx_t *qctx) { /* * Add the CNAME to the answer section. */ - if (qctx->sigrdataset != NULL) + if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) sigrdatasetp = &qctx->sigrdataset; if (WANTDNSSEC(qctx->client) && @@ -8066,7 +8421,7 @@ query_dname(query_ctx_t *qctx) { /* * Add the DNAME to the answer section. */ - if (qctx->sigrdataset != NULL) + if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) sigrdatasetp = &qctx->sigrdataset; if (WANTDNSSEC(qctx->client) && diff --git a/bin/named/server.c b/bin/named/server.c index b39662af2b..63f056121a 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -3733,6 +3733,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, if (view->maxncachettl > 7 * 24 * 3600) view->maxncachettl = 7 * 24 * 3600; + obj = NULL; + result = ns_config_get(maps, "synth-from-dnssec", &obj); + INSIST(result == ISC_R_SUCCESS); + view->synthfromdnssec = cfg_obj_asboolean(obj); + /* * Configure the view's cache. * diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index 4cf6979ebc..7fb1bbdb03 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -290,6 +290,9 @@ init_desc(void) { "resulted in a successful remote lookup", "QryNXRedirRLookup"); SET_NSSTATDESC(badcookie, "sent badcookie response", "QryBADCOOKIE"); + SET_NSSTATDESC(nxdomainsynth, "synthesized a NXDOMAIN response", "SynthNXDOMAIN"); + SET_NSSTATDESC(nodatasynth, "syththesized a no-data response", "SynthNODATA"); + SET_NSSTATDESC(wildcardsynth, "synthesized a wildcard response", "SynthWILDCARD"); INSIST(i == dns_nsstatscounter_max); /* Initialize resolver statistics */ diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf index 9b74657157..f11032b10b 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -61,8 +61,8 @@ options { serial-query-rate 100; server-id none; max-cache-size 20000000000000; - nta-recheck 604800; nta-lifetime 604800; + nta-recheck 604800; transfer-source 0.0.0.0 dscp 63; zone-statistics none; }; diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index 5e09f5d122..0bc6c6ee38 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -84,8 +84,9 @@ SUBDIRS="acl additional addzone allow_query autosign builtin pipelined @PKCS11_TEST@ reclimit redirect resolver rndc rpz rpzrecurse rrchecker rrl rrsetorder rsabigexponent runtime sfcache smartsign sortlist spf staticstub statistics - statschannel stub tcp tkey tools tsig tsiggss unknown upforwd - verify views wildcard xfer xferquota zero zonechecks" + statschannel stub synthfromdnssec tcp tkey tools tsig + tsiggss unknown upforwd verify views wildcard xfer xferquota + zero zonechecks" # Things that are different on Windows KILL=kill diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index fb4df20715..54bea5c3dd 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1754,7 +1754,7 @@ ret=0 $PERL -e 'my $delay = '$start' + 11 - time(); select(undef, undef, undef, $delay) if ($delay > 0);' # check nta table $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n._11 -lines=`wc -l < rndc.out.ns4.test$n._11` +lines=`grep " expiry " rndc.out.ns4.test$n._11 | wc -l` [ "$lines" -le 2 ] || ret=1 grep "bogus.example: expiry" rndc.out.ns4.test$n._11 > /dev/null || ret=1 grep "badds.example: expiry" rndc.out.ns4.test$n._11 > /dev/null && ret=1 @@ -1783,7 +1783,7 @@ $DIG $DIGOPTS c.bogus.example. a @10.53.0.4 > dig.out.ns4.test$n.15 || ret=1 grep "status: SERVFAIL" dig.out.ns4.test$n.15 > /dev/null || ret=1 # check nta table has been cleaned up now $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.3 -lines=`wc -l < rndc.out.ns4.test$n.3` +lines=`grep " expiry " rndc.out.ns4.test$n.3 | wc -l` [ "$lines" -eq 0 ] || ret=1 n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed - checking that all nta's have been lifted"; fi @@ -1845,12 +1845,12 @@ ret=0 n=`expr $n + 1` echo "I: testing NTA persistence across restarts ($n)" $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.1 -lines=`wc -l < rndc.out.ns4.test$n.1` +lines=`grep " expiry " rndc.out.ns4.test$n.1 | wc -l` [ "$lines" -eq 0 ] || ret=1 $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 30s bogus.example 2>&1 | sed 's/^/I:ns4 /' $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -f -l 10s badds.example 2>&1 | sed 's/^/I:ns4 /' $RNDC -c ../common/rndc.conf -s 10.53.0.4 -p 9953 nta -d > rndc.out.ns4.test$n.2 -lines=`wc -l < rndc.out.ns4.test$n.2` +lines=`grep " expiry " rndc.out.ns4.test$n.2 | wc -l` [ "$lines" -eq 2 ] || ret=1 start=`$PERL -e 'print time()."\n";'` diff --git a/bin/tests/system/synthfromdnssec/clean.sh b/bin/tests/system/synthfromdnssec/clean.sh new file mode 100644 index 0000000000..95099b9bfd --- /dev/null +++ b/bin/tests/system/synthfromdnssec/clean.sh @@ -0,0 +1,10 @@ +rm -f dig.out.* +rm -f ns1/K*+*+*.key +rm -f ns1/K*+*+*.private +rm -f ns1/dsset-* +rm -f ns1/example.db +rm -f ns1/example.db.signed +rm -f ns1/root.db +rm -f ns1/root.db.signed +rm -f ns1/trusted.conf +rm -f ns2/named_dump.db diff --git a/bin/tests/system/synthfromdnssec/ns1/example.db.in b/bin/tests/system/synthfromdnssec/ns1/example.db.in new file mode 100644 index 0000000000..f5a89d84b1 --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns1/example.db.in @@ -0,0 +1,7 @@ +$TTL 3600 +@ SOA ns1 hostmaster 1 3600 1200 604800 3600 +@ NS ns1 +ns1 A 10.53.0.1 +nodata TXT nodata +*.wild-a A 1.2.3.4 +*.wild-cname CNAME ns1 diff --git a/bin/tests/system/synthfromdnssec/ns1/named.conf b/bin/tests/system/synthfromdnssec/ns1/named.conf new file mode 100644 index 0000000000..5d355c99ed --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns1/named.conf @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 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/. + */ + +// NS1 + +controls { /* empty */ }; + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify yes; + dnssec-enable yes; + dnssec-validation yes; +}; + +zone "." { + type master; + file "root.db.signed"; +}; + +zone "example" { + type master; + file "example.db.signed"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/synthfromdnssec/ns1/root.db.in b/bin/tests/system/synthfromdnssec/ns1/root.db.in new file mode 100644 index 0000000000..f7d75b0130 --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns1/root.db.in @@ -0,0 +1,6 @@ +$TTL 3600 +@ SOA ns1 hostmaster 1 3600 1200 604800 3600 +@ NS ns1 +ns1 A 10.53.0.1 +example NS ns1.example +ns1.example A 10.53.0.1 diff --git a/bin/tests/system/synthfromdnssec/ns1/sign.sh b/bin/tests/system/synthfromdnssec/ns1/sign.sh new file mode 100644 index 0000000000..4a4b8057e5 --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns1/sign.sh @@ -0,0 +1,40 @@ +#!/bin/sh -e +# +# Copyright (C) 2017 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/. + +SYSTEMTESTTOP=../.. +. $SYSTEMTESTTOP/conf.sh + +zone=example +infile=example.db.in +zonefile=example.db + +keyname=`$KEYGEN -q -r $RANDFILE -a RSASHA256 -b 2048 -n zone $zone` +cat $infile $keyname.key > $zonefile + +$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null + +zone=. +infile=root.db.in +zonefile=root.db + +keyname=`$KEYGEN -q -r $RANDFILE -a RSAMD5 -b 1024 -n zone $zone` + +cat $infile $keyname.key > $zonefile + +$SIGNER -P -g -r $RANDFILE -o $zone $zonefile > /dev/null + +# Configure the resolving server with a trusted key. +cat $keyname.key | grep -v '^; ' | $PERL -n -e ' +local ($dn, $class, $type, $flags, $proto, $alg, @rest) = split; +local $key = join("", @rest); +print < trusted.conf diff --git a/bin/tests/system/synthfromdnssec/ns2/named.conf b/bin/tests/system/synthfromdnssec/ns2/named.conf new file mode 100644 index 0000000000..0052a1beee --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns2/named.conf @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 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/. + */ + +// NS2 + +controls { /* empty */ }; + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion yes; + notify no; + dnssec-enable yes; + dnssec-validation yes; +}; + +zone "." { + type hint; + file "root.hints"; +}; + +include "../ns1/trusted.conf"; +include "../../common/controls.conf"; diff --git a/bin/tests/system/synthfromdnssec/ns2/root.hints b/bin/tests/system/synthfromdnssec/ns2/root.hints new file mode 100644 index 0000000000..b4dbd6842f --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns2/root.hints @@ -0,0 +1,2 @@ +. NS ns1 +ns1 A 10.53.0.1 diff --git a/bin/tests/system/synthfromdnssec/ns3/named.conf b/bin/tests/system/synthfromdnssec/ns3/named.conf new file mode 100644 index 0000000000..ab9f202d0c --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns3/named.conf @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 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/. + */ + +// NS3 + +controls { /* empty */ }; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + recursion yes; + notify no; + dnssec-enable yes; + dnssec-validation yes; +}; + +zone "." { + type hint; + file "root.hints"; +}; + +zone "." { + type redirect; + file "redirect.db"; +}; + +include "../ns1/trusted.conf"; diff --git a/bin/tests/system/synthfromdnssec/ns3/redirect.db b/bin/tests/system/synthfromdnssec/ns3/redirect.db new file mode 100644 index 0000000000..e2fdc0c50c --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns3/redirect.db @@ -0,0 +1,17 @@ +; Copyright (C) 2011, 2016 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/. + +; $Id: redirect.db,v 1.3 2011/03/01 23:48:06 tbox Exp $ + +$TTL 300 +@ IN SOA ns.example.net hostmaster.example.net 0 0 0 0 0 +@ IN NS ns.example.net +; +; NS records do not need address records in this zone as it is not in the +; normal namespace. +; +*.redirect. IN A 100.100.100.2 +*.redirect. IN AAAA 2001:ffff:ffff::100.100.100.2 diff --git a/bin/tests/system/synthfromdnssec/ns3/root.hints b/bin/tests/system/synthfromdnssec/ns3/root.hints new file mode 100644 index 0000000000..b4dbd6842f --- /dev/null +++ b/bin/tests/system/synthfromdnssec/ns3/root.hints @@ -0,0 +1,2 @@ +. NS ns1 +ns1 A 10.53.0.1 diff --git a/bin/tests/system/synthfromdnssec/setup.sh b/bin/tests/system/synthfromdnssec/setup.sh new file mode 100644 index 0000000000..b8cfaf0532 --- /dev/null +++ b/bin/tests/system/synthfromdnssec/setup.sh @@ -0,0 +1,17 @@ +#!/bin/sh -e +# +# Copyright (C) 2000, 2001, 2004, 2007, 2009, 2011-2017 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/. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +$SHELL clean.sh + +test -r $RANDFILE || $GENRANDOM 800 $RANDFILE + +cd ns1 +$SHELL sign.sh diff --git a/bin/tests/system/synthfromdnssec/tests.sh b/bin/tests/system/synthfromdnssec/tests.sh new file mode 100644 index 0000000000..3aaba05d15 --- /dev/null +++ b/bin/tests/system/synthfromdnssec/tests.sh @@ -0,0 +1,139 @@ +#!/bin/sh +# +# Copyright (C) 2000-2002, 2004-2017 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/. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +status=0 +n=1 + +rm -f dig.out.* + +DIGOPTS="+tcp +noadd +nosea +nostat +nocmd +dnssec -p 5300" +DELVOPTS="-a ns1/trusted.conf -p 5300" + +echo "I:prime negative NXDOMAIN response ($n)" +ret=0 +$DIG $DIGOPTS a.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NXDOMAIN," dig.out.ns2.test$n > /dev/null || ret=1 +grep "example.*3600.IN.SOA" dig.out.ns2.test$n > /dev/null || ret=1 +nxdomain=dig.out.ns2.test$n +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:prime negative NODATA response ($n)" +ret=0 +$DIG $DIGOPTS nodata.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +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 +nodata=dig.out.ns2.test$n +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:prime wildcard response ($n)" +ret=0 +$DIG $DIGOPTS a.wild-a.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1 +grep "a.wild-a.example.*3600.IN.A" dig.out.ns2.test$n > /dev/null || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:prime wildcard CNAME response ($n)" +ret=0 +$DIG $DIGOPTS a.wild-cname.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1 +grep "a.wild-cname.example.*3600.IN.CNAME" dig.out.ns2.test$n > /dev/null || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:prime redirect response (+nodnssec) ($n)" +ret=0 +$DIG $DIGOPTS +nodnssec a.redirect. @10.53.0.3 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null && ret=1 +grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1 +grep 'a\.redirect\..*300.IN.A.100\.100\.100\.2' dig.out.ns2.test$n > /dev/null || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +sleep 1 + +$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 dumpdb + +echo "I:check synthesized NXDOMAIN response ($n)" +ret=0 +$DIG $DIGOPTS b.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NXDOMAIN," 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 $nxdomain dig.out.ns2.test$n || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:check synthesized NODATA response ($n)" +ret=0 +$DIG $DIGOPTS nodata.example. @10.53.0.2 aaaa > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +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` + +echo "I:check synthesized wildcard response ($n)" +ret=0 +$DIG $DIGOPTS b.wild-a.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1 +grep "b\.wild-a\.example\..*3600.IN.A" dig.out.ns2.test$n > /dev/null && 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` + +echo "I:check synthesized wildcard CNAME response ($n)" +ret=0 +$DIG $DIGOPTS b.wild-cname.example. @10.53.0.2 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1 +grep "b.wild-cname.example.*3600.IN.CNAME" dig.out.ns2.test$n > /dev/null && 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` + +echo "I:check redirect response (+dnssec) ($n)" +ret=0 +$DIG $DIGOPTS b.redirect. @10.53.0.3 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null || ret=1 +grep "status: NXDOMAIN," dig.out.ns2.test$n > /dev/null || ret=1 +grep "\..*3600.IN.SOA" dig.out.ns2.test$n > /dev/null && ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:check redirect response (+nodnssec) ($n)" +ret=0 +$DIG $DIGOPTS +nodnssec b.redirect. @10.53.0.3 a > dig.out.ns2.test$n || ret=1 +grep "flags:[^;]* ad[ ;]" dig.out.ns2.test$n > /dev/null && ret=1 +grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1 +grep 'b\.redirect\..*300.IN.A.100\.100\.100\.2' dig.out.ns2.test$n > /dev/null || ret=1 +n=`expr $n + 1` +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/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 486a76b912..0e63c3b2aa 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -1417,7 +1417,7 @@ controls { Changes that result from incoming incremental zone transfers are also - journalled in a similar way. + journaled in a similar way. @@ -2143,7 +2143,7 @@ allow-update { !{ !localnets; any; }; key host1-host2. ;}; Any keyset files corresponding to - secure subzones should be present. The zone signer will + secure sub-zones should be present. The zone signer will generate NSEC, NSEC3 and RRSIG records for the zone, as well as DS for the child zones if @@ -7313,6 +7313,38 @@ options { + + synth-from-dnssec + + + Synthesize answers from cached NSEC, NSEC3 and + other RRsets that have been proved to be correct + using DNSSEC. The default is yes. + + + Note: + + + + DNSSEC validation must be enabled for this + option to be effective. + + + + + This initial implementation only covers + NXDOMAIN synthesis from NSEC records. + Synthesis of NODATA and wildcard responses + is also planned, as is synthesis from NSEC3 + records. All of these will be controlled + by synth-from-dnssec. + + + + + + + diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml index 3ebdde35e4..407096e9eb 100644 --- a/doc/arm/notes.xml +++ b/doc/arm/notes.xml @@ -392,6 +392,21 @@ "[ECS address/source/scope]". + + + named will now synthesize responses + from cached DNSSEC-verified records. This will reduce + query loads on authoritative servers for signed domains: + if existing cached records can be used to determine + the answer then no query needs to be sent. + + + This behavior is controlled by the new + named.conf option + synth-from-dnssec. It is enabled by + default. + + diff --git a/doc/misc/options b/doc/misc/options index aaf1278c70..dfd5386680 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -174,9 +174,9 @@ options { fetches-per-server [ ( drop | fail ) ]; fetches-per-zone [ ( drop | fail ) ]; files ( default | unlimited | ); - filter-aaaa { ; ... }; // not configured - filter-aaaa-on-v4 ( break-dnssec | ); // not configured - filter-aaaa-on-v6 ( break-dnssec | ); // not configured + filter-aaaa { ; ... }; + filter-aaaa-on-v4 ( break-dnssec | ); + filter-aaaa-on-v6 ( break-dnssec | ); flush-zones-on-shutdown ; forward ( first | only ); forwarders [ port ] [ dscp ] { ( @@ -188,8 +188,8 @@ options { fstrm-set-output-queue-model ( mpsc | spsc ); // not configured fstrm-set-output-queue-size ; // not configured fstrm-set-reopen-interval ; // not configured - geoip-directory ( | none ); // not configured - geoip-use-ecs ; // not configured + geoip-directory ( | none ); + geoip-use-ecs ; glue-cache ; has-old-clients ; // obsolete heartbeat-interval ; @@ -208,7 +208,7 @@ options { listen-on-v6 [ port ] [ dscp ] { ; ... }; // may occur multiple times - lmdb-mapsize ; // non-operational + lmdb-mapsize ; lock-file ( | none ); maintain-ixfr-base ; // obsolete managed-keys-directory ; @@ -338,6 +338,7 @@ options { statistics-file ; statistics-interval ; // not yet implemented suppress-initial-notify ; // not yet implemented + synth-from-dnssec ; tcp-advertised-timeout ; tcp-clients ; tcp-idle-timeout ; @@ -515,9 +516,9 @@ view [ ] { fetch-quota-params ; fetches-per-server [ ( drop | fail ) ]; fetches-per-zone [ ( drop | fail ) ]; - filter-aaaa { ; ... }; // not configured - filter-aaaa-on-v4 ( break-dnssec | ); // not configured - filter-aaaa-on-v6 ( break-dnssec | ); // not configured + filter-aaaa { ; ... }; + filter-aaaa-on-v4 ( break-dnssec | ); + filter-aaaa-on-v6 ( break-dnssec | ); forward ( first | only ); forwarders [ port ] [ dscp ] { ( | ) [ port ] [ dscp ]; ... }; @@ -530,7 +531,7 @@ view [ ] { }; // may occur multiple times key-directory ; lame-ttl ; - lmdb-mapsize ; // non-operational + lmdb-mapsize ; maintain-ixfr-base ; // obsolete managed-keys { @@ -674,6 +675,7 @@ view [ ] { sig-validity-interval [ ]; sortlist { ; ... }; suppress-initial-notify ; // not yet implemented + synth-from-dnssec ; topology { ; ... }; // not implemented transfer-format ( many-answers | one-answer ); transfer-source ( | * ) [ port ( | * ) ] [ diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 4f7980f264..908ed24b81 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -120,6 +120,7 @@ struct dns_view { isc_boolean_t enablevalidation; isc_boolean_t acceptexpired; isc_boolean_t requireservercookie; + isc_boolean_t synthfromdnssec; isc_boolean_t trust_anchor_telemetry; dns_transfer_format_t transfer_format; dns_acl_t * cacheacl; diff --git a/lib/dns/message.c b/lib/dns/message.c index b53a473896..d3629b34b8 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -2629,14 +2629,19 @@ dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) { } void -dns_message_puttempname(dns_message_t *msg, dns_name_t **item) { - REQUIRE(DNS_MESSAGE_VALID(msg)); - REQUIRE(item != NULL && *item != NULL); +dns_message_puttempname(dns_message_t *msg, dns_name_t **itemp) { + dns_name_t *item; - if (dns_name_dynamic(*item)) - dns_name_free(*item, msg->mctx); - isc_mempool_put(msg->namepool, *item); - *item = NULL; + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(itemp != NULL && *itemp != NULL); + item = *itemp; + REQUIRE(!ISC_LINK_LINKED(item, link)); + REQUIRE(ISC_LIST_HEAD(item->list) == NULL); + + *itemp = NULL; + if (dns_name_dynamic(item)) + dns_name_free(item, msg->mctx); + isc_mempool_put(msg->namepool, item); } void diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index f7da35be8c..8242fa7063 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -4830,7 +4830,8 @@ find_coveringnsec(rbtdb_search_t *search, dns_dbnode_t **nodep, foundsig = NULL; empty_node = ISC_TRUE; header_prev = NULL; - for (header = node->data; header != NULL; header = header_next) { + for (header = node->data; header != NULL; header = header_next) + { header_next = header->next; if (check_stale_header(node, header, &locktype, lock, search, @@ -4842,7 +4843,15 @@ find_coveringnsec(rbtdb_search_t *search, dns_dbnode_t **nodep, header_prev = header; continue; } - empty_node = ISC_FALSE; + /* + * Don't stop on provable noqname / RRSIG. + */ + if (header->noqname == NULL && + RBTDB_RDATATYPE_BASE(header->type) + != dns_rdatatype_rrsig) + { + empty_node = ISC_FALSE; + } if (header->type == matchtype) found = header; else if (header->type == sigmatchtype) @@ -4873,7 +4882,6 @@ find_coveringnsec(rbtdb_search_t *search, dns_dbnode_t **nodep, return (result); } - static isc_result_t cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index ca3674bd42..f48094a784 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -5024,6 +5024,8 @@ validated(isc_task_t *task, isc_event_t *event) { isc_uint32_t ttl; unsigned options; isc_uint32_t bucketnum; + dns_fixedname_t fwild; + dns_name_t *wild = NULL; UNUSED(task); /* for now */ @@ -5048,8 +5050,14 @@ validated(isc_task_t *task, isc_event_t *event) { /* * Destroy the validator early so that we can - * destroy the fctx if necessary. + * destroy the fctx if necessary. Save the wildcard name. */ + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + dns_fixedname_init(&fwild); + wild = dns_fixedname_name(&fwild); + dns_name_copy(dns_fixedname_name(&vevent->validator->wild), + wild, NULL); + } dns_validator_destroy(&vevent->validator); isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); @@ -5324,7 +5332,7 @@ validated(isc_task_t *task, isc_event_t *event) { answer_response: /* - * Cache any NS/NSEC records that happened to be validated. + * Cache any SOA/NS/NSEC records that happened to be validated. */ result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY); while (result == ISC_R_SUCCESS) { @@ -5335,6 +5343,7 @@ validated(isc_task_t *task, isc_event_t *event) { rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if ((rdataset->type != dns_rdatatype_ns && + rdataset->type != dns_rdatatype_soa && rdataset->type != dns_rdatatype_nsec) || rdataset->trust != dns_trust_secure) continue; @@ -5369,6 +5378,37 @@ validated(isc_task_t *task, isc_event_t *event) { DNS_SECTION_AUTHORITY); } + /* + * Add the wild card entry. + */ + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL && + vevent->rdataset != NULL && + dns_rdataset_isassociated(vevent->rdataset) && + vevent->rdataset->trust == dns_trust_secure && + vevent->sigrdataset != NULL && + dns_rdataset_isassociated(vevent->sigrdataset) && + vevent->sigrdataset->trust == dns_trust_secure && + wild != NULL) + { + dns_dbnode_t *wnode = NULL; + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(wild, namebuf, sizeof(namebuf)); + + fprintf(stderr, "save wildcard data %s\n", namebuf); + result = dns_db_findnode(fctx->cache, wild, ISC_TRUE, &wnode); + if (result == ISC_R_SUCCESS) + result = dns_db_addrdataset(fctx->cache, wnode, NULL, + now, vevent->rdataset, 0, + NULL); + if (result == ISC_R_SUCCESS) + result = dns_db_addrdataset(fctx->cache, wnode, NULL, + now, vevent->sigrdataset, + 0, NULL); + if (wnode != NULL) + dns_db_detachnode(fctx->cache, &wnode); + } + result = ISC_R_SUCCESS; /* diff --git a/lib/dns/view.c b/lib/dns/view.c index 935cac41f9..40bb475b3d 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -232,6 +232,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, view->requestnsid = ISC_FALSE; view->sendcookie = ISC_TRUE; view->requireservercookie = ISC_FALSE; + view->synthfromdnssec = ISC_TRUE; view->trust_anchor_telemetry = ISC_TRUE; view->new_zone_dir = NULL; view->new_zone_file = NULL; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 4cd0401431..c2caf72825 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1891,8 +1891,6 @@ view_clauses[] = { #else { "lmdb-mapsize", &cfg_type_sizeval, CFG_CLAUSEFLAG_NOOP }, #endif - { "nocookie-udp-size", &cfg_type_uint32, 0 }, - { "nosit-udp-size", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, { "max-acache-size", &cfg_type_sizenodefault, CFG_CLAUSEFLAG_OBSOLETE }, { "max-cache-size", &cfg_type_sizeorpercent, 0 }, @@ -1902,17 +1900,19 @@ view_clauses[] = { { "max-recursion-depth", &cfg_type_uint32, 0 }, { "max-recursion-queries", &cfg_type_uint32, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, + { "message-compression", &cfg_type_boolean, 0 }, { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP }, { "minimal-any", &cfg_type_boolean, 0 }, { "minimal-responses", &cfg_type_minimal, 0 }, { "new-zones-directory", &cfg_type_qstring, 0 }, - { "nta-recheck", &cfg_type_ttlval, 0 }, - { "nta-lifetime", &cfg_type_ttlval, 0 }, - { "nxdomain-redirect", &cfg_type_astring, 0 }, - { "prefetch", &cfg_type_prefetch, 0 }, - { "preferred-glue", &cfg_type_astring, 0 }, { "no-case-compress", &cfg_type_bracketed_aml, 0 }, - { "message-compression", &cfg_type_boolean, 0 }, + { "nocookie-udp-size", &cfg_type_uint32, 0 }, + { "nosit-udp-size", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, + { "nta-lifetime", &cfg_type_ttlval, 0 }, + { "nta-recheck", &cfg_type_ttlval, 0 }, + { "nxdomain-redirect", &cfg_type_astring, 0 }, + { "preferred-glue", &cfg_type_astring, 0 }, + { "prefetch", &cfg_type_prefetch, 0 }, { "provide-ixfr", &cfg_type_boolean, 0 }, /* * Note that the query-source option syntax is different @@ -1938,6 +1938,7 @@ view_clauses[] = { { "servfail-ttl", &cfg_type_ttlval, 0 }, { "sortlist", &cfg_type_bracketed_aml, 0 }, { "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, + { "synth-from-dnssec", &cfg_type_boolean, 0 }, { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_NOTIMP }, { "transfer-format", &cfg_type_transferformat, 0 }, { "trust-anchor-telemetry", &cfg_type_boolean,