Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft-ietf-dnsop-dns-error-reporting #902

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/example.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,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.
#
Expand Down
25 changes: 19 additions & 6 deletions doc/unbound.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -1996,17 +1996,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
Expand Down
104 changes: 104 additions & 0 deletions services/mesh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,106 @@ 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;

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

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

/* 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);
mesh_add_sub(qstate, &qinfo, BIT_RD, 0, 0, &newq, &sub);
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;
Expand All @@ -1517,6 +1617,10 @@ void mesh_query_done(struct mesh_state* mstate)
if(err) { log_err("%s", err); }
}
}

if(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);
Expand Down
1 change: 1 addition & 0 deletions sldns/rrdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,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;
Expand Down
176 changes: 176 additions & 0 deletions testdata/dns_error_reporting.rpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
; 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

stub-zone:
name: a.domain
stub-addr: 0.0.0.1
stub-zone:
name: an.agent
stub-addr: 0.0.0.2
CONFIG_END

SCENARIO_BEGIN Test DNS Error Reporting

; 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)
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
5 changes: 4 additions & 1 deletion util/config_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ config_create(void)
cfg->serve_expired_ttl_reset = 0;
cfg->serve_expired_reply_ttl = 30;
cfg->serve_expired_client_timeout = 0;
cfg->ede_serve_expired = 0;
cfg->serve_original_ttl = 0;
cfg->zonemd_permissive_mode = 0;
cfg->add_holddown = 30*24*3600;
Expand Down Expand Up @@ -407,6 +406,8 @@ config_create(void)
cfg->ipset_name_v6 = NULL;
#endif
cfg->ede = 0;
cfg->ede_serve_expired = 0;
cfg->dns_error_reporting = 0;
return cfg;
error_exit:
config_delete(cfg);
Expand Down Expand Up @@ -717,6 +718,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_YNO("serve-original-ttl:", serve_original_ttl)
else S_STR("val-nsec3-keysize-iterations:", val_nsec3_key_iterations)
else S_YNO("zonemd-permissive-mode:", zonemd_permissive_mode)
Expand Down Expand Up @@ -1183,6 +1185,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_YNO(opt, "serve-original-ttl", serve_original_ttl)
else O_STR(opt, "val-nsec3-keysize-iterations",val_nsec3_key_iterations)
else O_YNO(opt, "zonemd-permissive-mode", zonemd_permissive_mode)
Expand Down
Loading