Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7a68d77
v1 EDER poc
Jul 18, 2022
b19ff18
remove superfluous edns_list_get_option function
Jul 19, 2022
4ee82e3
create an EDER configurable
Jul 22, 2022
f19a192
Hackathon 114
gthess Aug 8, 2022
a5912f3
Merge branch 'master' into features/error-reporting-willem
wtoorop Nov 4, 2022
cb6bdd8
Fixes for version -04
wtoorop Nov 5, 2022
f47ee5d
Merge pull request #776 from NLnetLabs/features/error-reporting-willem
wtoorop Nov 5, 2022
9236e6e
Merge branch 'master' into features/error-reporting-poc
wtoorop Jun 20, 2023
a3f3da4
Merge branch 'master' into features/error-reporting-poc
gthess Jul 20, 2024
308847a
Generated configparser and configlexer are not versioned in master an…
gthess Jul 20, 2024
ea17c0a
- Remove NOERROR DNS Error Reporting; not part of final RFC.
gthess Jul 20, 2024
d509375
Fix buffer protection and agent domain validity
gthess Jul 20, 2024
e9a4f31
Use DNS Error Reporting instead of the eder nickname
gthess Jul 21, 2024
c472337
- Update documentation.
gthess Jul 21, 2024
d5d7a62
- Fix typo.
gthess Jul 21, 2024
7d9f2c9
- Bail out early if ede is not present.
gthess Jul 21, 2024
56200cd
- Forget previous EDNS options from upstream; this is what was
gthess Mar 15, 2025
bb15329
- Don't report LDNS_EDE_OTHER and bail early if there is no reporting
gthess Mar 15, 2025
1dc588d
Merge branch 'master' into features/error-reporting-poc
gthess Mar 15, 2025
7de7fd5
- Only do DNS error reporting when a client asked for something that
gthess Mar 16, 2025
a79c06e
- Add an error reporting agent in the parent that should be ignored.
gthess Mar 16, 2025
6fd74e7
- review feedback.
gthess Mar 19, 2025
ee32263
Merge branch 'master' into features/error-reporting-poc
gthess Apr 4, 2025
3a09064
- fixup for fast reload
gthess Apr 4, 2025
e9a0af1
Add 'num.dns_error_reports' to stats and test for it.
gthess Apr 4, 2025
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
3 changes: 3 additions & 0 deletions daemon/remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions daemon/stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
Expand Down
5 changes: 5 additions & 0 deletions doc/example.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down
6 changes: 6 additions & 0 deletions doc/unbound-control.8.in
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
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 @@ -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
Expand Down
1 change: 1 addition & 0 deletions iterator/iterator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions libunbound/unbound.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand Down
117 changes: 117 additions & 0 deletions services/mesh.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions services/mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions sldns/rrdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 6 additions & 5 deletions smallapp/unbound-control.c
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
Loading