mirror of
https://github.com/NLnetLabs/unbound.git
synced 2026-02-02 19:59:28 -05:00
DNS Error Reporting (RFC 9567) (#902)
* v1 EDER poc * remove superfluous edns_list_get_option function * create an EDER configurable * Hackathon 114 * Fixes for version -04 * Generated configparser and configlexer are not versioned in master anymore * Remove NOERROR DNS Error Reporting; not part of final RFC. * Use assigned IANA EDNS0 Option Code for Report-Channel. * Fix buffer protection and agent domain validity * Use DNS Error Reporting instead of the eder nickname * Update documentation. * Fix typo. * Bail out early if ede is not present. * Forget previous EDNS options from upstream; this is what was implicitly happening but not deterministacally. * Don't report LDNS_EDE_OTHER and bail early if there is no reporting agent. * Only do DNS error reporting when a client asked for something that went wrong. * Add an error reporting agent in the parent that should be ignored. * review feedback. * fixup for fast reload * Add 'num.dns_error_reports' to stats and test for it. --------- Co-authored-by: TCY16 <tom@nlnetlabs.nl> Co-authored-by: Yorgos Thessalonikefs <yorgos@nlnetlabs.nl>
This commit is contained in:
parent
eb390dd038
commit
a616437338
20 changed files with 469 additions and 23 deletions
|
|
@ -824,6 +824,8 @@ print_stats(RES* ssl, const char* nm, struct ub_stats_info* s)
|
|||
if(!ssl_printf(ssl, "%s.num.dnscrypt.malformed"SQ"%lu\n", nm,
|
||||
(unsigned long)s->svr.num_query_dnscrypt_crypted_malformed)) return 0;
|
||||
#endif
|
||||
if(!ssl_printf(ssl, "%s.num.dns_error_reports"SQ"%lu\n", nm,
|
||||
(unsigned long)s->svr.num_dns_error_reports)) return 0;
|
||||
if(!ssl_printf(ssl, "%s.requestlist.avg"SQ"%g\n", nm,
|
||||
(s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)?
|
||||
(double)s->svr.sum_query_list_size/
|
||||
|
|
@ -5639,6 +5641,7 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg,
|
|||
COPY_VAR_int(serve_expired_reply_ttl);
|
||||
COPY_VAR_int(serve_expired_client_timeout);
|
||||
COPY_VAR_int(ede_serve_expired);
|
||||
COPY_VAR_int(dns_error_reporting);
|
||||
COPY_VAR_int(serve_original_ttl);
|
||||
COPY_VAR_ptr(val_nsec3_key_iterations);
|
||||
COPY_VAR_int(zonemd_permissive_mode);
|
||||
|
|
|
|||
|
|
@ -285,6 +285,8 @@ server_stats_compile(struct worker* worker, struct ub_stats_info* s, int reset)
|
|||
(long long)worker->env.mesh->num_queries_discard_timeout;
|
||||
s->svr.num_queries_wait_limit +=
|
||||
(long long)worker->env.mesh->num_queries_wait_limit;
|
||||
s->svr.num_dns_error_reports +=
|
||||
(long long)worker->env.mesh->num_dns_error_reports;
|
||||
/* values from outside network */
|
||||
s->svr.unwanted_replies = (long long)worker->back->unwanted_replies;
|
||||
s->svr.qtcp_outgoing = (long long)worker->back->num_tcp_outgoing;
|
||||
|
|
@ -446,6 +448,7 @@ void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* a)
|
|||
total->svr.num_queries_discard_timeout +=
|
||||
a->svr.num_queries_discard_timeout;
|
||||
total->svr.num_queries_wait_limit += a->svr.num_queries_wait_limit;
|
||||
total->svr.num_dns_error_reports += a->svr.num_dns_error_reports;
|
||||
total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache;
|
||||
total->svr.num_queries_prefetch += a->svr.num_queries_prefetch;
|
||||
total->svr.num_queries_timed_out += a->svr.num_queries_timed_out;
|
||||
|
|
@ -458,9 +461,9 @@ void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* a)
|
|||
#ifdef USE_DNSCRYPT
|
||||
total->svr.num_query_dnscrypt_crypted += a->svr.num_query_dnscrypt_crypted;
|
||||
total->svr.num_query_dnscrypt_cert += a->svr.num_query_dnscrypt_cert;
|
||||
total->svr.num_query_dnscrypt_cleartext += \
|
||||
total->svr.num_query_dnscrypt_cleartext +=
|
||||
a->svr.num_query_dnscrypt_cleartext;
|
||||
total->svr.num_query_dnscrypt_crypted_malformed += \
|
||||
total->svr.num_query_dnscrypt_crypted_malformed +=
|
||||
a->svr.num_query_dnscrypt_crypted_malformed;
|
||||
#endif /* USE_DNSCRYPT */
|
||||
/* the max size reached is upped to higher of both */
|
||||
|
|
|
|||
|
|
@ -1086,6 +1086,11 @@ server:
|
|||
# Note that the ede option above needs to be enabled for this to work.
|
||||
# ede-serve-expired: no
|
||||
|
||||
# Enable DNS Error Reporting (RFC9567).
|
||||
# qname-minimisation is advised to be turned on as well to increase
|
||||
# privacy on the outgoing reports.
|
||||
# dns-error-reporting: no
|
||||
|
||||
# Specific options for ipsecmod. Unbound needs to be configured with
|
||||
# --enable-ipsecmod for these to take effect.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -534,6 +534,9 @@ request for certificates.
|
|||
.I threadX.num.dnscrypt.malformed
|
||||
number of request that were neither cleartext, not valid dnscrypt messages.
|
||||
.TP
|
||||
.I threadX.num.dns_error_reports
|
||||
number of DNS Error Reports generated by thread
|
||||
.TP
|
||||
.I threadX.num.prefetch
|
||||
number of cache prefetches performed. This number is included in
|
||||
cachehits, as the original query had the unprefetched answer from cache,
|
||||
|
|
@ -628,6 +631,9 @@ summed over threads.
|
|||
.I total.num.dnscrypt.malformed
|
||||
summed over threads.
|
||||
.TP
|
||||
.I total.num.dns_error_reports
|
||||
summed over threads.
|
||||
.TP
|
||||
.I total.num.prefetch
|
||||
summed over threads.
|
||||
.TP
|
||||
|
|
|
|||
|
|
@ -2089,17 +2089,30 @@ be used. Default is 65001.
|
|||
.TP 5
|
||||
.B ede: \fI<yes or no>
|
||||
If enabled, Unbound will respond with Extended DNS Error codes (RFC8914).
|
||||
These EDEs attach informative error messages to a response for various
|
||||
errors. Default is "no".
|
||||
These EDEs provide additional information with a response mainly for, but not
|
||||
limited to, DNS and DNSSEC errors.
|
||||
|
||||
When the \fBval-log-level\fR option is also set to \fB2\fR, responses with
|
||||
Extended DNS Errors concerning DNSSEC failures that are not served from cache,
|
||||
will also contain a descriptive text message about the reason for the failure.
|
||||
Extended DNS Errors concerning DNSSEC failures will also contain a descriptive
|
||||
text message about the reason for the failure.
|
||||
Default is "no".
|
||||
.TP 5
|
||||
.B ede\-serve\-expired: \fI<yes or no>
|
||||
If enabled, Unbound will attach an Extended DNS Error (RFC8914) Code 3 - Stale
|
||||
Answer as EDNS0 option to the expired response. Note that this will not attach
|
||||
the EDE code without setting the global \fBede\fR option to "yes" as well.
|
||||
Answer as EDNS0 option to the expired response.
|
||||
The \fBede\fR option needs to be enabled as well for this to work.
|
||||
Default is "no".
|
||||
.TP 5
|
||||
.B dns\-error\-reporting: \fI<yes or no>
|
||||
If enabled, Unbound will send DNS Error Reports (RFC9567).
|
||||
The name servers need to express support by attaching the Report-Channel EDNS0
|
||||
option on their replies specifying the reporting agent for the zone.
|
||||
Any errors encountered during resolution that would result in Unbound
|
||||
generating an Extended DNS Error (RFC8914) will be reported to the zone's
|
||||
reporting agent.
|
||||
The \fBede\fR option does not need to be enabled for this to work.
|
||||
It is advised that the \fBqname\-minimisation\fR option is also enabled to
|
||||
increase privacy on the outgoing reports.
|
||||
Default is "no".
|
||||
.SS "Remote Control Options"
|
||||
In the
|
||||
|
|
|
|||
|
|
@ -4332,6 +4332,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq,
|
|||
}
|
||||
|
||||
/* Copy the edns options we may got from the back end */
|
||||
qstate->edns_opts_back_in = NULL;
|
||||
if(edns.opt_list_in) {
|
||||
qstate->edns_opts_back_in = edns_opt_copy_region(edns.opt_list_in,
|
||||
qstate->region);
|
||||
|
|
|
|||
|
|
@ -853,6 +853,8 @@ struct ub_server_stats {
|
|||
long long num_queries_discard_timeout;
|
||||
/** number of queries removed due to wait-limit */
|
||||
long long num_queries_wait_limit;
|
||||
/** number of dns error reports generated */
|
||||
long long num_dns_error_reports;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
117
services/mesh.c
117
services/mesh.c
|
|
@ -232,6 +232,7 @@ mesh_create(struct module_stack* stack, struct module_env* env)
|
|||
mesh->ans_cachedb = 0;
|
||||
mesh->num_queries_discard_timeout = 0;
|
||||
mesh->num_queries_wait_limit = 0;
|
||||
mesh->num_dns_error_reports = 0;
|
||||
mesh->max_reply_states = env->cfg->num_queries_per_thread;
|
||||
mesh->max_forever_states = (mesh->max_reply_states+1)/2;
|
||||
#ifndef S_SPLINT_S
|
||||
|
|
@ -1582,6 +1583,117 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the DNS Error Report (RFC9567).
|
||||
* If there is an EDE attached for this reply and there was a Report-Channel
|
||||
* EDNS0 option from the upstream, fire up a report query.
|
||||
* @param qstate: module qstate.
|
||||
* @param rep: prepared reply to be sent.
|
||||
*/
|
||||
static void dns_error_reporting(struct module_qstate* qstate,
|
||||
struct reply_info* rep)
|
||||
{
|
||||
struct query_info qinfo;
|
||||
struct mesh_state* sub;
|
||||
struct module_qstate* newq;
|
||||
uint8_t buf[LDNS_MAX_DOMAINLEN];
|
||||
size_t count = 0;
|
||||
int written;
|
||||
size_t expected_length;
|
||||
struct edns_option* opt;
|
||||
sldns_ede_code reason_bogus = LDNS_EDE_NONE;
|
||||
sldns_rr_type qtype = qstate->qinfo.qtype;
|
||||
uint8_t* qname = qstate->qinfo.qname;
|
||||
size_t qname_len = qstate->qinfo.qname_len-1; /* skip the trailing \0 */
|
||||
uint8_t* agent_domain;
|
||||
size_t agent_domain_len;
|
||||
|
||||
/* We need a valid reporting agent;
|
||||
* this is based on qstate->edns_opts_back_in that will probably have
|
||||
* the latest reporting agent we found while iterating */
|
||||
opt = edns_opt_list_find(qstate->edns_opts_back_in,
|
||||
LDNS_EDNS_REPORT_CHANNEL);
|
||||
if(!opt) return;
|
||||
agent_domain_len = opt->opt_len;
|
||||
agent_domain = opt->opt_data;
|
||||
if(dname_valid(agent_domain, agent_domain_len) < 3) {
|
||||
/* The agent domain needs to be a valid dname that is not the
|
||||
* root; from RFC9567. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the EDE generated from the mesh state, these are mostly
|
||||
* validator errors. If other errors are produced in the future (e.g.,
|
||||
* RPZ) we would not want them to result in error reports. */
|
||||
reason_bogus = errinf_to_reason_bogus(qstate);
|
||||
if(rep && ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS &&
|
||||
rep->reason_bogus != LDNS_EDE_NONE) ||
|
||||
reason_bogus == LDNS_EDE_NONE)) {
|
||||
reason_bogus = rep->reason_bogus;
|
||||
}
|
||||
if(reason_bogus == LDNS_EDE_NONE ||
|
||||
/* other, does not make sense without the text that comes
|
||||
* with it */
|
||||
reason_bogus == LDNS_EDE_OTHER) return;
|
||||
|
||||
/* Synthesize the error report query in the format:
|
||||
* "_er.$qtype.$qname.$ede._er.$reporting-agent-domain" */
|
||||
/* First check if the static length parts fit in the buffer.
|
||||
* That is everything except for qtype and ede that need to be
|
||||
* converted to decimal and checked further on. */
|
||||
expected_length = 4/*_er*/+qname_len+4/*_er*/+agent_domain_len;
|
||||
if(expected_length > LDNS_MAX_DOMAINLEN) goto skip;
|
||||
|
||||
memmove(buf+count, "\3_er", 4);
|
||||
count += 4;
|
||||
|
||||
written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count,
|
||||
"X%d", qtype);
|
||||
expected_length += written;
|
||||
/* Skip on error, truncation or long expected length */
|
||||
if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count ||
|
||||
expected_length > LDNS_MAX_DOMAINLEN ) goto skip;
|
||||
/* Put in the label length */
|
||||
*(buf+count) = (char)(written - 1);
|
||||
count += written;
|
||||
|
||||
memmove(buf+count, qname, qname_len);
|
||||
count += qname_len;
|
||||
|
||||
written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count,
|
||||
"X%d", reason_bogus);
|
||||
expected_length += written;
|
||||
/* Skip on error, truncation or long expected length */
|
||||
if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count ||
|
||||
expected_length > LDNS_MAX_DOMAINLEN ) goto skip;
|
||||
*(buf+count) = (char)(written - 1);
|
||||
count += written;
|
||||
|
||||
memmove(buf+count, "\3_er", 4);
|
||||
count += 4;
|
||||
|
||||
/* Copy the agent domain */
|
||||
memmove(buf+count, agent_domain, agent_domain_len);
|
||||
count += agent_domain_len;
|
||||
|
||||
qinfo.qname = buf;
|
||||
qinfo.qname_len = count;
|
||||
qinfo.qtype = LDNS_RR_TYPE_TXT;
|
||||
qinfo.qclass = qstate->qinfo.qclass;
|
||||
qinfo.local_alias = NULL;
|
||||
|
||||
log_query_info(VERB_ALGO, "DNS Error Reporting: generating report "
|
||||
"query for", &qinfo);
|
||||
if(mesh_add_sub(qstate, &qinfo, BIT_RD, 0, 0, &newq, &sub)) {
|
||||
qstate->env->mesh->num_dns_error_reports++;
|
||||
}
|
||||
return;
|
||||
skip:
|
||||
verbose(VERB_ALGO, "DNS Error Reporting: report query qname too long; "
|
||||
"skip");
|
||||
return;
|
||||
}
|
||||
|
||||
void mesh_query_done(struct mesh_state* mstate)
|
||||
{
|
||||
struct mesh_reply* r;
|
||||
|
|
@ -1610,6 +1722,10 @@ void mesh_query_done(struct mesh_state* mstate)
|
|||
if(err) { log_err("%s", err); }
|
||||
}
|
||||
}
|
||||
|
||||
if(mstate->reply_list && mstate->s.env->cfg->dns_error_reporting)
|
||||
dns_error_reporting(&mstate->s, rep);
|
||||
|
||||
for(r = mstate->reply_list; r; r = r->next) {
|
||||
struct timeval old;
|
||||
timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
|
||||
|
|
@ -2156,6 +2272,7 @@ mesh_stats_clear(struct mesh_area* mesh)
|
|||
mesh->ans_nodata = 0;
|
||||
mesh->num_queries_discard_timeout = 0;
|
||||
mesh->num_queries_wait_limit = 0;
|
||||
mesh->num_dns_error_reports = 0;
|
||||
}
|
||||
|
||||
size_t
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ struct mesh_area {
|
|||
size_t num_queries_discard_timeout;
|
||||
/** stats, number of queries removed due to wait-limit */
|
||||
size_t num_queries_wait_limit;
|
||||
/** stats, number of dns error reports generated */
|
||||
size_t num_dns_error_reports;
|
||||
|
||||
/** backup of query if other operations recurse and need the
|
||||
* network buffers */
|
||||
|
|
|
|||
|
|
@ -443,6 +443,7 @@ enum sldns_enum_edns_option
|
|||
LDNS_EDNS_PADDING = 12, /* RFC7830 */
|
||||
LDNS_EDNS_EDE = 15, /* RFC8914 */
|
||||
LDNS_EDNS_CLIENT_TAG = 16, /* draft-bellis-dnsop-edns-tags-01 */
|
||||
LDNS_EDNS_REPORT_CHANNEL = 18, /* RFC9567 */
|
||||
LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST = 65534
|
||||
};
|
||||
typedef enum sldns_enum_edns_option sldns_edns_option;
|
||||
|
|
|
|||
|
|
@ -244,12 +244,13 @@ static void pr_stats(const char* nm, struct ub_stats_info* s)
|
|||
PR_UL_NM("num.expired", s->svr.ans_expired);
|
||||
PR_UL_NM("num.recursivereplies", s->mesh_replies_sent);
|
||||
#ifdef USE_DNSCRYPT
|
||||
PR_UL_NM("num.dnscrypt.crypted", s->svr.num_query_dnscrypt_crypted);
|
||||
PR_UL_NM("num.dnscrypt.cert", s->svr.num_query_dnscrypt_cert);
|
||||
PR_UL_NM("num.dnscrypt.cleartext", s->svr.num_query_dnscrypt_cleartext);
|
||||
PR_UL_NM("num.dnscrypt.malformed",
|
||||
s->svr.num_query_dnscrypt_crypted_malformed);
|
||||
PR_UL_NM("num.dnscrypt.crypted", s->svr.num_query_dnscrypt_crypted);
|
||||
PR_UL_NM("num.dnscrypt.cert", s->svr.num_query_dnscrypt_cert);
|
||||
PR_UL_NM("num.dnscrypt.cleartext", s->svr.num_query_dnscrypt_cleartext);
|
||||
PR_UL_NM("num.dnscrypt.malformed",
|
||||
s->svr.num_query_dnscrypt_crypted_malformed);
|
||||
#endif /* USE_DNSCRYPT */
|
||||
PR_UL_NM("num.dns_error_reports", s->svr.num_dns_error_reports);
|
||||
printf("%s.requestlist.avg"SQ"%g\n", nm,
|
||||
(s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)?
|
||||
(double)s->svr.sum_query_list_size/
|
||||
|
|
|
|||
200
testdata/dns_error_reporting.rpl
vendored
Normal file
200
testdata/dns_error_reporting.rpl
vendored
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
; Test DNS Error Reporting.
|
||||
|
||||
server:
|
||||
module-config: "validator iterator"
|
||||
trust-anchor-signaling: no
|
||||
target-fetch-policy: "0 0 0 0 0"
|
||||
verbosity: 4
|
||||
qname-minimisation: no
|
||||
minimal-responses: no
|
||||
rrset-roundrobin: no
|
||||
trust-anchor: "a.domain DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29B22446B1"
|
||||
ede: no # It is not needed for dns-error-reporting; only for clients to receive EDEs
|
||||
dns-error-reporting: yes
|
||||
do-ip6: no
|
||||
|
||||
stub-zone:
|
||||
name: domain
|
||||
stub-addr: 0.0.0.0
|
||||
stub-zone:
|
||||
name: an.agent
|
||||
stub-addr: 0.0.0.2
|
||||
CONFIG_END
|
||||
|
||||
SCENARIO_BEGIN Test DNS Error Reporting
|
||||
|
||||
; domain
|
||||
RANGE_BEGIN 0 100
|
||||
ADDRESS 0.0.0.0
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR NOERROR
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
SECTION AUTHORITY
|
||||
a.domain. IN NS ns.a.domain.
|
||||
SECTION ADDITIONAL
|
||||
ns.a.domain. IN A 0.0.0.1
|
||||
HEX_EDNSDATA_BEGIN
|
||||
00 12 ; opt-code (Report-Channel)
|
||||
00 0A ; opt-len
|
||||
02 61 6E 05 61 67 65 6E 74 00 ; an.agent.
|
||||
HEX_EDNSDATA_END
|
||||
ENTRY_END
|
||||
RANGE_END
|
||||
|
||||
; a.domain
|
||||
RANGE_BEGIN 0 9
|
||||
ADDRESS 0.0.0.1
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR NOERROR
|
||||
SECTION QUESTION
|
||||
a.domain. IN DNSKEY
|
||||
ENTRY_END
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR NOERROR
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
SECTION ANSWER
|
||||
a.domain. 5 IN A 0.0.0.0
|
||||
; No RRSIG to trigger validation error (and EDE)
|
||||
SECTION ADDITIONAL
|
||||
; No Report-Channel here
|
||||
ENTRY_END
|
||||
RANGE_END
|
||||
|
||||
; a.domain
|
||||
RANGE_BEGIN 10 100
|
||||
ADDRESS 0.0.0.1
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR NOERROR
|
||||
SECTION QUESTION
|
||||
a.domain. IN DNSKEY
|
||||
ENTRY_END
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR NOERROR
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
SECTION ANSWER
|
||||
a.domain. 5 IN A 0.0.0.0
|
||||
; No RRSIG to trigger validator error and EDE
|
||||
SECTION ADDITIONAL
|
||||
HEX_EDNSDATA_BEGIN
|
||||
00 12 ; opt-code (Report-Channel)
|
||||
00 0A ; opt-len
|
||||
02 61 6E 05 61 67 65 6E 74 00 ; an.agent.
|
||||
HEX_EDNSDATA_END
|
||||
ENTRY_END
|
||||
RANGE_END
|
||||
|
||||
; an.agent
|
||||
RANGE_BEGIN 10 20
|
||||
ADDRESS 0.0.0.2
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
ADJUST copy_id
|
||||
REPLY QR NOERROR
|
||||
SECTION QUESTION
|
||||
_er.1.a.domain.9._er.an.agent. IN TXT
|
||||
SECTION ANSWER
|
||||
_er.1.a.domain.9._er.an.agent. IN TXT "OK"
|
||||
ENTRY_END
|
||||
RANGE_END
|
||||
|
||||
; Query
|
||||
STEP 0 QUERY
|
||||
ENTRY_BEGIN
|
||||
REPLY RD
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
ENTRY_END
|
||||
|
||||
; Check that validation failed (no DNS error reporting at this state;
|
||||
; 'domain' did give an error reporting agent, but the latest upstream
|
||||
; 'a.domain' did not)
|
||||
STEP 1 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH all
|
||||
REPLY QR RD RA SERVFAIL
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
ENTRY_END
|
||||
|
||||
; Wait for the a.domain query to expire (TTL 5)
|
||||
STEP 3 TIME_PASSES ELAPSE 6
|
||||
|
||||
; Query again
|
||||
STEP 10 QUERY
|
||||
ENTRY_BEGIN
|
||||
REPLY RD
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
ENTRY_END
|
||||
|
||||
; Check that validation failed
|
||||
; (a DNS Error Report query should have been generated)
|
||||
STEP 11 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH all
|
||||
REPLY QR RD RA SERVFAIL
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
ENTRY_END
|
||||
|
||||
; Check explicitly that the DNS Error Report query is cached.
|
||||
STEP 20 QUERY
|
||||
ENTRY_BEGIN
|
||||
REPLY RD
|
||||
SECTION QUESTION
|
||||
_er.1.a.domain.9._er.an.agent. IN TXT
|
||||
ENTRY_END
|
||||
|
||||
; At this range there are no configured agents to answer this.
|
||||
; If the DNS Error Report query is not answered from the cache the test will
|
||||
; fail with pending messages.
|
||||
STEP 21 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH all
|
||||
REPLY RD QR RA NOERROR
|
||||
SECTION QUESTION
|
||||
_er.1.a.domain.9._er.an.agent. IN TXT
|
||||
SECTION ANSWER
|
||||
_er.1.a.domain.9._er.an.agent. IN TXT "OK"
|
||||
ENTRY_END
|
||||
|
||||
; Wait for the a.domain query to expire (5 TTL).
|
||||
; The DNS Error Report query should still be cached (SOA negative).
|
||||
STEP 30 TIME_PASSES ELAPSE 6
|
||||
|
||||
; Force a DNS Error Report query generation again.
|
||||
STEP 31 QUERY
|
||||
ENTRY_BEGIN
|
||||
REPLY RD
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
ENTRY_END
|
||||
|
||||
; Check that validation failed
|
||||
STEP 32 CHECK_ANSWER
|
||||
ENTRY_BEGIN
|
||||
MATCH all
|
||||
REPLY QR RD RA SERVFAIL
|
||||
SECTION QUESTION
|
||||
a.domain. IN A
|
||||
ENTRY_END
|
||||
|
||||
; The same DNS Error Report query will be generated as above.
|
||||
; No agent is configured at this range to answer the DNS Error Report query.
|
||||
; If the DNS Error Report query is not used from the cache the test will fail
|
||||
; with pending messages.
|
||||
|
||||
SCENARIO_END
|
||||
9
testdata/stat_values.tdir/stat_values.conf
vendored
9
testdata/stat_values.tdir/stat_values.conf
vendored
|
|
@ -15,6 +15,9 @@ server:
|
|||
root-key-sentinel: no
|
||||
trust-anchor-signaling: no
|
||||
serve-expired-client-timeout: 0
|
||||
dns-error-reporting: yes
|
||||
|
||||
trust-anchor: "bogusdnssec. DS 1444 8 2 5224fb17d630a2e3efdc863a05a4032c5db415b5de3f32472ee9abed42e10146"
|
||||
|
||||
local-zone: local.zone static
|
||||
local-data: "www.local.zone A 192.0.2.1"
|
||||
|
|
@ -30,6 +33,12 @@ remote-control:
|
|||
stub-zone:
|
||||
name: "example.com."
|
||||
stub-addr: "127.0.0.1@@TOPORT@"
|
||||
stub-zone:
|
||||
name: "bogusdnssec."
|
||||
stub-addr: "127.0.0.1@@TOPORT@"
|
||||
stub-zone:
|
||||
name: "an.agent."
|
||||
stub-addr: "127.0.0.1@@TOPORT@"
|
||||
stub-zone:
|
||||
name: "expired."
|
||||
stub-addr: "127.0.0.1@@EXPIREDPORT@"
|
||||
|
|
|
|||
33
testdata/stat_values.tdir/stat_values.test
vendored
33
testdata/stat_values.tdir/stat_values.test
vendored
|
|
@ -426,6 +426,35 @@ rrset.cache.count=3
|
|||
infra.cache.count=2"
|
||||
|
||||
|
||||
teststep "Check dns-error-reporting."
|
||||
echo "> dig www.bogusdnssec."
|
||||
dig @127.0.0.1 -p $UNBOUND_PORT www.bogusdnssec. | tee outfile
|
||||
echo "> check answer"
|
||||
if grep "SERVFAIL" outfile; then
|
||||
echo "OK"
|
||||
else
|
||||
end 1
|
||||
fi
|
||||
check_stats "\
|
||||
infra.cache.count=4
|
||||
key.cache.count=1
|
||||
msg.cache.count=7
|
||||
num.answer.bogus=1
|
||||
num.answer.rcode.SERVFAIL=1
|
||||
num.query.class.IN=1
|
||||
num.query.edns.present=1
|
||||
num.query.flags.AD=1
|
||||
num.query.flags.RD=1
|
||||
num.query.opcode.QUERY=1
|
||||
num.query.type.A=1
|
||||
num.query.udpout=9
|
||||
rrset.cache.count=4
|
||||
total.num.cachemiss=1
|
||||
total.num.dns_error_reports=1
|
||||
total.num.queries=1
|
||||
total.num.recursivereplies=1"
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Bring the discard-timeout, wait-limit configured Unbound up
|
||||
|
|
@ -436,8 +465,8 @@ bring_up_alternate_configuration ub_discard_wait_limit.conf
|
|||
|
||||
|
||||
teststep "Check discard-timeout and wait-limit"
|
||||
echo "> dig www.slow"
|
||||
dig @127.0.0.1 -p $UNBOUND_PORT +retry=2 +timeout=1 www.slow. | tee outfile
|
||||
echo "> dig www.unresponsive"
|
||||
dig @127.0.0.1 -p $UNBOUND_PORT +retry=2 +timeout=1 www.unresponsive. | tee outfile
|
||||
echo "> check answer"
|
||||
if grep "no servers could be reached" outfile; then
|
||||
echo "OK"
|
||||
|
|
|
|||
45
testdata/stat_values.tdir/stat_values.testns
vendored
45
testdata/stat_values.tdir/stat_values.testns
vendored
|
|
@ -32,14 +32,51 @@ SECTION ANSWER
|
|||
0ttl 0 IN A 0.0.0.1
|
||||
ENTRY_END
|
||||
|
||||
$ORIGIN slow.
|
||||
|
||||
|
||||
$ORIGIN bogusdnssec.
|
||||
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
REPLY QR AA NOERROR
|
||||
ADJUST copy_id sleep=2
|
||||
ADJUST copy_id
|
||||
SECTION QUESTION
|
||||
www. IN A
|
||||
@ IN DNSKEY
|
||||
SECTION ANSWER
|
||||
www. 0 IN A 10.20.30.40
|
||||
ENTRY_END
|
||||
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode qtype qname
|
||||
REPLY QR AA NOERROR
|
||||
ADJUST copy_id
|
||||
SECTION QUESTION
|
||||
www IN A
|
||||
SECTION ANSWER
|
||||
www 0 IN A 10.20.30.40
|
||||
; bogus signature to not trigger LAME DNSSEC and continue with validation
|
||||
www 0 IN RRSIG A 8 2 240 20250429005000 20250401005000 42393 bogusdnssec. ob6ddTJkdeOUn92cxx1NPGneV7rhOp2zKBv8FXQjJ/Wso8LJJnzRHW9p 3sTatlzi+UdRi7BOrcxwjUG38lgO+TS5vRFGAiTRmOezm6xJVNTg8lIb RJGCD5bRtRRstwt31Qt6Gda+6sAyvDebpUB/opkQpevv6xohdrhr0g8+ Q4w=
|
||||
SECTION ADDITIONAL
|
||||
; dns error reporting agent
|
||||
HEX_EDNSDATA_BEGIN
|
||||
00 12 ; opt-code (Report-Channel)
|
||||
00 0A ; opt-len
|
||||
02 61 6E 05 61 67 65 6E 74 00 ; an.agent.
|
||||
HEX_EDNSDATA_END
|
||||
ENTRY_END
|
||||
|
||||
|
||||
|
||||
$ORIGIN an.agent.
|
||||
;just give an answer back to anything
|
||||
ENTRY_BEGIN
|
||||
MATCH opcode subdomain
|
||||
REPLY QR AA NXDOMAIN
|
||||
ADJUST copy_id copy_query
|
||||
SECTION QUESTION
|
||||
an.agent. IN ANY
|
||||
ENTRY_END
|
||||
|
||||
|
||||
|
||||
$ORIGIN unresponsive.
|
||||
;; no entry for 'unresponsive.', we rely on timeouts.
|
||||
|
|
|
|||
|
|
@ -32,5 +32,5 @@ remote-control:
|
|||
control-key-file: "unbound_control.key"
|
||||
control-cert-file: "unbound_control.pem"
|
||||
stub-zone:
|
||||
name: "slow."
|
||||
name: "unresponsive."
|
||||
stub-addr: "127.0.0.1@@TOPORT@"
|
||||
|
|
|
|||
|
|
@ -284,7 +284,6 @@ config_create(void)
|
|||
cfg->serve_expired_ttl_reset = 0;
|
||||
cfg->serve_expired_reply_ttl = 30;
|
||||
cfg->serve_expired_client_timeout = 1800;
|
||||
cfg->ede_serve_expired = 0;
|
||||
cfg->serve_original_ttl = 0;
|
||||
cfg->zonemd_permissive_mode = 0;
|
||||
cfg->add_holddown = 30*24*3600;
|
||||
|
|
@ -418,6 +417,8 @@ config_create(void)
|
|||
cfg->ipset_name_v6 = NULL;
|
||||
#endif
|
||||
cfg->ede = 0;
|
||||
cfg->ede_serve_expired = 0;
|
||||
cfg->dns_error_reporting = 0;
|
||||
cfg->iter_scrub_ns = 20;
|
||||
cfg->iter_scrub_cname = 11;
|
||||
cfg->max_global_quota = 200;
|
||||
|
|
@ -756,6 +757,7 @@ int config_set_option(struct config_file* cfg, const char* opt,
|
|||
else S_NUMBER_OR_ZERO("serve-expired-client-timeout:", serve_expired_client_timeout)
|
||||
else S_YNO("ede:", ede)
|
||||
else S_YNO("ede-serve-expired:", ede_serve_expired)
|
||||
else S_YNO("dns-error-reporting:", dns_error_reporting)
|
||||
else S_NUMBER_OR_ZERO("iter-scrub-ns:", iter_scrub_ns)
|
||||
else S_NUMBER_OR_ZERO("iter-scrub-cname:", iter_scrub_cname)
|
||||
else S_NUMBER_OR_ZERO("max-global-quota:", max_global_quota)
|
||||
|
|
@ -1231,6 +1233,7 @@ config_get_option(struct config_file* cfg, const char* opt,
|
|||
else O_DEC(opt, "serve-expired-client-timeout", serve_expired_client_timeout)
|
||||
else O_YNO(opt, "ede", ede)
|
||||
else O_YNO(opt, "ede-serve-expired", ede_serve_expired)
|
||||
else O_YNO(opt, "dns-error-reporting", dns_error_reporting)
|
||||
else O_DEC(opt, "iter-scrub-ns", iter_scrub_ns)
|
||||
else O_DEC(opt, "iter-scrub-cname", iter_scrub_cname)
|
||||
else O_DEC(opt, "max-global-quota", max_global_quota)
|
||||
|
|
|
|||
|
|
@ -438,8 +438,6 @@ struct config_file {
|
|||
/** serve expired entries only after trying to update the entries and this
|
||||
* timeout (in milliseconds) is reached */
|
||||
int serve_expired_client_timeout;
|
||||
/** serve EDE code 3 - Stale Answer (RFC8914) for expired entries */
|
||||
int ede_serve_expired;
|
||||
/** serve original TTLs rather than decrementing ones */
|
||||
int serve_original_ttl;
|
||||
/** nsec3 maximum iterations per key size, string */
|
||||
|
|
@ -784,6 +782,10 @@ struct config_file {
|
|||
#endif
|
||||
/** respond with Extended DNS Errors (RFC8914) */
|
||||
int ede;
|
||||
/** serve EDE code 3 - Stale Answer (RFC8914) for expired entries */
|
||||
int ede_serve_expired;
|
||||
/** send DNS Error Reports to upstream reporting agent (RFC9567) */
|
||||
int dns_error_reporting;
|
||||
/** limit on NS RRs in RRset for the iterator scrubber. */
|
||||
size_t iter_scrub_ns;
|
||||
/** limit on CNAME, DNAME RRs in answer for the iterator scrubber. */
|
||||
|
|
|
|||
|
|
@ -601,6 +601,7 @@ edns-client-string{COLON} { YDVAR(2, VAR_EDNS_CLIENT_STRING) }
|
|||
edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
|
||||
nsid{COLON} { YDVAR(1, VAR_NSID ) }
|
||||
ede{COLON} { YDVAR(1, VAR_EDE ) }
|
||||
dns-error-reporting{COLON} { YDVAR(1, VAR_DNS_ERROR_REPORTING ) }
|
||||
proxy-protocol-port{COLON} { YDVAR(1, VAR_PROXY_PROTOCOL_PORT) }
|
||||
iter-scrub-ns{COLON} { YDVAR(1, VAR_ITER_SCRUB_NS) }
|
||||
iter-scrub-cname{COLON} { YDVAR(1, VAR_ITER_SCRUB_CNAME) }
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ extern struct config_parser_state* cfg_parser;
|
|||
%token VAR_EDNS_CLIENT_STRING_OPCODE VAR_NSID
|
||||
%token VAR_ZONEMD_PERMISSIVE_MODE VAR_ZONEMD_CHECK VAR_ZONEMD_REJECT_ABSENCE
|
||||
%token VAR_RPZ_SIGNAL_NXDOMAIN_RA VAR_INTERFACE_AUTOMATIC_PORTS VAR_EDE
|
||||
%token VAR_DNS_ERROR_REPORTING
|
||||
%token VAR_INTERFACE_ACTION VAR_INTERFACE_VIEW VAR_INTERFACE_TAG
|
||||
%token VAR_INTERFACE_TAG_ACTION VAR_INTERFACE_TAG_DATA
|
||||
%token VAR_QUIC_PORT VAR_QUIC_SIZE
|
||||
|
|
@ -350,6 +351,7 @@ content_server: server_num_threads | server_verbosity | server_port |
|
|||
server_tcp_reuse_timeout | server_tcp_auth_query_timeout |
|
||||
server_quic_port | server_quic_size |
|
||||
server_interface_automatic_ports | server_ede |
|
||||
server_dns_error_reporting |
|
||||
server_proxy_protocol_port | server_statistics_inhibit_zero |
|
||||
server_harden_unknown_additional | server_disable_edns_do |
|
||||
server_log_destaddr | server_cookie_secret_file |
|
||||
|
|
@ -3073,6 +3075,15 @@ server_ede: VAR_EDE STRING_ARG
|
|||
free($2);
|
||||
}
|
||||
;
|
||||
server_dns_error_reporting: VAR_DNS_ERROR_REPORTING STRING_ARG
|
||||
{
|
||||
OUTYY(("P(server_dns_error_reporting:%s)\n", $2));
|
||||
if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
|
||||
yyerror("expected yes or no.");
|
||||
else cfg_parser->cfg->dns_error_reporting = (strcmp($2, "yes")==0);
|
||||
free($2);
|
||||
}
|
||||
;
|
||||
server_proxy_protocol_port: VAR_PROXY_PROTOCOL_PORT STRING_ARG
|
||||
{
|
||||
OUTYY(("P(server_proxy_protocol_port:%s)\n", $2));
|
||||
|
|
|
|||
Loading…
Reference in a new issue