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:
Willem Toorop 2025-04-07 10:25:10 +02:00 committed by GitHub
parent eb390dd038
commit a616437338
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 469 additions and 23 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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