aboutsummaryrefslogtreecommitdiff
path: root/daemon/worker.c
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2022-06-08 14:43:13 +0000
committerCy Schubert <cy@FreeBSD.org>2022-06-08 14:43:13 +0000
commit5f9f82264b91e041df7cba2406625146e7268ce4 (patch)
treeba7309ee547bf22115420277f45a3478aafb6397 /daemon/worker.c
parent3574dc0bd83e731bba79edc130c0569bf05f7af5 (diff)
downloadsrc-5f9f82264b91e041df7cba2406625146e7268ce4.tar.gz
src-5f9f82264b91e041df7cba2406625146e7268ce4.zip
unbound: Vendor import 1.16.0vendor/unbound/1.16.0
Diffstat (limited to 'daemon/worker.c')
-rw-r--r--daemon/worker.c284
1 files changed, 249 insertions, 35 deletions
diff --git a/daemon/worker.c b/daemon/worker.c
index 862affb24e9a..bf8c5d6b6763 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -98,7 +98,7 @@
/** ratelimit for error responses */
#define ERROR_RATELIMIT 100 /* qps */
-/**
+/**
* seconds to add to prefetch leeway. This is a TTL that expires old rrsets
* earlier than they should in order to put the new update into the cache.
* This additional value is to make sure that if not all TTLs are equal in
@@ -484,6 +484,12 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
msg->rep, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
worker->env.now_tv))
return 0;
+ /* TODO store the reason for the bogus reply in cache
+ * and implement in here instead of the hardcoded EDE */
+ if (worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+ worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
+ }
error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
&msg->qinfo, id, flags, edns);
if(worker->stats.extended) {
@@ -553,7 +559,7 @@ apply_respip_action(struct worker* worker, const struct query_info* qinfo,
return 1;
if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo,
- alias_rrset, 0, worker->scratchpad, az))
+ alias_rrset, 0, worker->scratchpad, az, NULL))
return 0;
/* xxx_deny actions mean dropping the reply, unless the original reply
@@ -654,6 +660,12 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
worker->env.now_tv))
goto bail_out;
+ /* TODO store the reason for the bogus reply in cache
+ * and implement in here instead of the hardcoded EDE */
+ if (worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+ worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
+ }
error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
qinfo, id, flags, edns);
rrset_array_unlock_touch(worker->env.rrset_cache,
@@ -716,15 +728,25 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
if(!*partial_repp)
goto bail_out;
}
- } else if(!reply_info_answer_encode(qinfo, encode_rep, id, flags,
- repinfo->c->buffer, timenow, 1, worker->scratchpad,
- udpsize, edns, (int)(edns->bits & EDNS_DO), *is_secure_answer)) {
- if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL,
- LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
- worker->env.now_tv))
- edns->opt_list_inplace_cb_out = NULL;
- error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
- qinfo, id, flags, edns);
+ } else {
+ /* We don't check the global ede as this is a warning, not
+ * an error */
+ if (*is_expired_answer == 1 &&
+ worker->env.cfg->ede_serve_expired && worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+ worker->scratchpad, LDNS_EDE_STALE_ANSWER, "");
+ }
+ if(!reply_info_answer_encode(qinfo, encode_rep, id, flags,
+ repinfo->c->buffer, timenow, 1, worker->scratchpad,
+ udpsize, edns, (int)(edns->bits & EDNS_DO),
+ *is_secure_answer)) {
+ if(!inplace_cb_reply_servfail_call(&worker->env, qinfo,
+ NULL, NULL, LDNS_RCODE_SERVFAIL, edns, repinfo,
+ worker->scratchpad, worker->env.now_tv))
+ edns->opt_list_inplace_cb_out = NULL;
+ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ qinfo, id, flags, edns);
+ }
}
/* cannot send the reply right now, because blocking network syscall
* is bad while holding locks. */
@@ -741,10 +763,12 @@ bail_out:
/** Reply to client and perform prefetch to keep cache up to date. */
static void
-reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
- uint16_t flags, struct comm_reply* repinfo, time_t leeway, int noreply)
+reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
+ uint16_t flags, struct comm_reply* repinfo, time_t leeway, int noreply,
+ int rpz_passthru, struct edns_option* opt_list)
{
- /* first send answer to client to keep its latency
+ (void)opt_list;
+ /* first send answer to client to keep its latency
* as small as a cachereply */
if(!noreply) {
if(repinfo->c->tcp_req_info) {
@@ -755,13 +779,23 @@ reply_and_prefetch(struct worker* worker, struct query_info* qinfo,
comm_point_send_reply(repinfo);
}
server_stats_prefetch(&worker->stats, worker);
-
+#ifdef CLIENT_SUBNET
+ /* Check if the subnet module is enabled. In that case pass over the
+ * comm_reply information for ECS generation later. The mesh states are
+ * unique when subnet is enabled. */
+ if(modstack_find(&worker->env.mesh->mods, "subnetcache") != -1
+ && worker->env.unique_mesh) {
+ mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+ PREFETCH_EXPIRY_ADD, rpz_passthru, repinfo, opt_list);
+ return;
+ }
+#endif
/* create the prefetch in the mesh as a normal lookup without
* client addrs waiting, which has the cache blacklisted (to bypass
* the cache and go to the network for the data). */
/* this (potentially) runs the mesh for the new query */
- mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
- PREFETCH_EXPIRY_ADD);
+ mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+ PREFETCH_EXPIRY_ADD, rpz_passthru, NULL, NULL);
}
/**
@@ -1012,32 +1046,178 @@ answer_notify(struct worker* w, struct query_info* qinfo,
static int
deny_refuse(struct comm_point* c, enum acl_access acl,
enum acl_access deny, enum acl_access refuse,
- struct worker* worker, struct comm_reply* repinfo)
+ struct worker* worker, struct comm_reply* repinfo,
+ struct acl_addr* acladdr, int ede)
{
if(acl == deny) {
+ if(verbosity >= VERB_ALGO) {
+ log_acl_action("dropped", &repinfo->addr,
+ repinfo->addrlen, acl, acladdr);
+ log_buf(VERB_ALGO, "dropped", c->buffer);
+ }
comm_point_drop_reply(repinfo);
if(worker->stats.extended)
worker->stats.unwanted_queries++;
return 0;
} else if(acl == refuse) {
- log_addr(VERB_ALGO, "refused query from",
- &repinfo->addr, repinfo->addrlen);
- log_buf(VERB_ALGO, "refuse", c->buffer);
+ size_t opt_rr_mark;
+
+ if(verbosity >= VERB_ALGO) {
+ log_acl_action("refused", &repinfo->addr,
+ repinfo->addrlen, acl, acladdr);
+ log_buf(VERB_ALGO, "refuse", c->buffer);
+ }
+
if(worker->stats.extended)
worker->stats.unwanted_queries++;
if(worker_check_request(c->buffer, worker) == -1) {
comm_point_drop_reply(repinfo);
return 0; /* discard this */
}
- sldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE);
- sldns_buffer_write_at(c->buffer, 4,
- (uint8_t*)"\0\0\0\0\0\0\0\0", 8);
+ /* worker_check_request() above guarantees that the buffer contains at
+ * least a header and that qdcount == 1
+ */
+ log_assert(sldns_buffer_limit(c->buffer) >= LDNS_HEADER_SIZE
+ && LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) == 1);
+
+ sldns_buffer_skip(c->buffer, LDNS_HEADER_SIZE); /* skip header */
+
+ /* check additional section is present and that we respond with EDEs */
+ if(LDNS_ARCOUNT(sldns_buffer_begin(c->buffer)) != 1
+ || !ede) {
+ LDNS_QDCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_REFUSED);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ }
+
+ if (!query_dname_len(c->buffer)) {
+ LDNS_QDCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ }
+ /* space available for query type and class? */
+ if (sldns_buffer_remaining(c->buffer) < 2 * sizeof(uint16_t)) {
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ LDNS_QDCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ }
LDNS_QR_SET(sldns_buffer_begin(c->buffer));
LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
LDNS_RCODE_REFUSED);
- sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
+
+ sldns_buffer_skip(c->buffer, (ssize_t)sizeof(uint16_t)); /* skip qtype */
+
+ sldns_buffer_skip(c->buffer, (ssize_t)sizeof(uint16_t)); /* skip qclass */
+
+ /* The OPT RR to be returned should come directly after
+ * the query, so mark this spot.
+ */
+ opt_rr_mark = sldns_buffer_position(c->buffer);
+
+ /* Skip through the RR records */
+ if(LDNS_ANCOUNT(sldns_buffer_begin(c->buffer)) != 0 ||
+ LDNS_NSCOUNT(sldns_buffer_begin(c->buffer)) != 0) {
+ if(!skip_pkt_rrs(c->buffer,
+ ((int)LDNS_ANCOUNT(sldns_buffer_begin(c->buffer)))+
+ ((int)LDNS_NSCOUNT(sldns_buffer_begin(c->buffer))))) {
+ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ sldns_buffer_set_position(c->buffer, opt_rr_mark);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ }
+ }
+ /* Do we have a valid OPT RR here? If not return REFUSED (could be a valid TSIG or something so no FORMERR) */
+ /* domain name must be the root of length 1. */
+ if(sldns_buffer_remaining(c->buffer) < 1 || *sldns_buffer_current(c->buffer) != 0) {
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ sldns_buffer_set_position(c->buffer, opt_rr_mark);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ } else {
+ sldns_buffer_skip(c->buffer, 1); /* skip root label */
+ }
+ if(sldns_buffer_remaining(c->buffer) < 2 ||
+ sldns_buffer_read_u16(c->buffer) != LDNS_RR_TYPE_OPT) {
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ sldns_buffer_set_position(c->buffer, opt_rr_mark);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ }
+ /* Write OPT RR directly after the query,
+ * so without the (possibly skipped) Answer and NS RRs
+ */
+ LDNS_ANCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ LDNS_NSCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ sldns_buffer_clear(c->buffer); /* reset write limit */
+ sldns_buffer_set_position(c->buffer, opt_rr_mark);
+
+ /* Check if OPT record can be written
+ * 17 == root label (1) + RR type (2) + UDP Size (2)
+ * + Fields (4) + rdata len (2) + EDE Option code (2)
+ * + EDE Option length (2) + EDE info-code (2)
+ */
+ if (sldns_buffer_available(c->buffer, 17) == 0) {
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 0);
+ sldns_buffer_flip(c->buffer);
+ return 1;
+ }
+
+ LDNS_ARCOUNT_SET(sldns_buffer_begin(c->buffer), 1);
+
+ /* root label */
+ sldns_buffer_write_u8(c->buffer, 0);
+ sldns_buffer_write_u16(c->buffer, LDNS_RR_TYPE_OPT);
+ sldns_buffer_write_u16(c->buffer, EDNS_ADVERTISED_SIZE);
+
+ /* write OPT Record TTL Field */
+ sldns_buffer_write_u32(c->buffer, 0);
+
+ /* write rdata len: EDE option + length + info-code */
+ sldns_buffer_write_u16(c->buffer, 6);
+
+ /* write OPTIONS; add EDE option code */
+ sldns_buffer_write_u16(c->buffer, LDNS_EDNS_EDE);
+
+ /* write single EDE option length (for just 1 info-code) */
+ sldns_buffer_write_u16(c->buffer, 2);
+
+ /* write single EDE info-code */
+ sldns_buffer_write_u16(c->buffer, LDNS_EDE_PROHIBITED);
+
sldns_buffer_flip(c->buffer);
+
+ verbose(VERB_ALGO, "attached EDE code: %d", LDNS_EDE_PROHIBITED);
+
return 1;
+
}
return -1;
@@ -1045,16 +1225,20 @@ deny_refuse(struct comm_point* c, enum acl_access acl,
static int
deny_refuse_all(struct comm_point* c, enum acl_access acl,
- struct worker* worker, struct comm_reply* repinfo)
+ struct worker* worker, struct comm_reply* repinfo,
+ struct acl_addr* acladdr, int ede)
{
- return deny_refuse(c, acl, acl_deny, acl_refuse, worker, repinfo);
+ return deny_refuse(c, acl, acl_deny, acl_refuse, worker, repinfo,
+ acladdr, ede);
}
static int
deny_refuse_non_local(struct comm_point* c, enum acl_access acl,
- struct worker* worker, struct comm_reply* repinfo)
+ struct worker* worker, struct comm_reply* repinfo,
+ struct acl_addr* acladdr, int ede)
{
- return deny_refuse(c, acl, acl_deny_non_local, acl_refuse_non_local, worker, repinfo);
+ return deny_refuse(c, acl, acl_deny_non_local, acl_refuse_non_local,
+ worker, repinfo, acladdr, ede);
}
int
@@ -1067,12 +1251,14 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
struct lruhash_entry* e;
struct query_info qinfo;
struct edns_data edns;
+ struct edns_option* original_edns_list = NULL;
enum acl_access acl;
struct acl_addr* acladdr;
int rc = 0;
int need_drop = 0;
int is_expired_answer = 0;
int is_secure_answer = 0;
+ int rpz_passthru = 0;
/* We might have to chase a CNAME chain internally, in which case
* we'll have up to two replies and combine them to build a complete
* answer. These variables control this case. */
@@ -1145,7 +1331,9 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
acladdr = acl_addr_lookup(worker->daemon->acl, &repinfo->addr,
repinfo->addrlen);
acl = acl_get_control(acladdr);
- if((ret=deny_refuse_all(c, acl, worker, repinfo)) != -1)
+
+ if((ret=deny_refuse_all(c, acl, worker, repinfo, acladdr,
+ worker->env.cfg->ede)) != -1)
{
if(ret == 1)
goto send_reply;
@@ -1338,7 +1526,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
if(worker->env.auth_zones &&
rpz_callback_from_worker_request(worker->env.auth_zones,
&worker->env, &qinfo, &edns, c->buffer, worker->scratchpad,
- repinfo, acladdr->taglist, acladdr->taglen, &worker->stats)) {
+ repinfo, acladdr->taglist, acladdr->taglen, &worker->stats,
+ &rpz_passthru)) {
regional_free_all(worker->scratchpad);
if(sldns_buffer_limit(c->buffer) == 0) {
comm_point_drop_reply(repinfo);
@@ -1364,7 +1553,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
/* We've looked in our local zones. If the answer isn't there, we
* might need to bail out based on ACLs now. */
- if((ret=deny_refuse_non_local(c, acl, worker, repinfo)) != -1)
+ if((ret=deny_refuse_non_local(c, acl, worker, repinfo, acladdr,
+ worker->env.cfg->ede)) != -1)
{
regional_free_all(worker->scratchpad);
if(ret == 1)
@@ -1383,12 +1573,17 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
* ACLs allow the snooping. */
if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) &&
acl != acl_allow_snoop ) {
+ if (worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
+ worker->scratchpad, LDNS_EDE_NOT_AUTHORITATIVE, "");
+ }
error_encode(c->buffer, LDNS_RCODE_REFUSED, &qinfo,
*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
- sldns_buffer_read_u16_at(c->buffer, 2), NULL);
+ sldns_buffer_read_u16_at(c->buffer, 2), &edns);
regional_free_all(worker->scratchpad);
log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from",
&repinfo->addr, repinfo->addrlen);
+
goto send_reply;
}
@@ -1429,6 +1624,11 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
cinfo = &cinfo_tmp;
}
+ /* Keep the original edns list around. The pointer could change if there is
+ * a cached answer (through the inplace callback function there).
+ * No need to actually copy the contents as they shouldn't change.
+ * Used while prefetching and subnet is enabled. */
+ original_edns_list = edns.opt_list_in;
lookup_cache:
/* Lookup the cache. In case we chase an intermediate CNAME chain
* this is a two-pass operation, and lookup_qinfo is different for
@@ -1461,10 +1661,13 @@ lookup_cache:
< *worker->env.now)
leeway = 0;
lock_rw_unlock(&e->lock);
+
reply_and_prefetch(worker, lookup_qinfo,
sldns_buffer_read_u16_at(c->buffer, 2),
repinfo, leeway,
- (partial_rep || need_drop));
+ (partial_rep || need_drop),
+ rpz_passthru,
+ original_edns_list);
if(!partial_rep) {
rc = 0;
regional_free_all(worker->scratchpad);
@@ -1501,6 +1704,7 @@ lookup_cache:
verbose(VERB_ALGO, "answer from the cache failed");
lock_rw_unlock(&e->lock);
}
+
if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) {
if(answer_norec_from_cache(worker, &qinfo,
*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
@@ -1527,7 +1731,8 @@ lookup_cache:
/* grab a work request structure for this new request */
mesh_new_client(worker->env.mesh, &qinfo, cinfo,
sldns_buffer_read_u16_at(c->buffer, 2),
- &edns, repinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer));
+ &edns, repinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+ rpz_passthru);
regional_free_all(worker->scratchpad);
worker_mem_report(worker, NULL);
return 0;
@@ -1592,6 +1797,9 @@ worker_sighandler(int sig, void* arg)
comm_base_exit(worker->base);
break;
#endif
+#ifdef SIGBREAK
+ case SIGBREAK:
+#endif
case SIGINT:
worker->need_to_exit = 1;
comm_base_exit(worker->base);
@@ -1710,6 +1918,9 @@ worker_init(struct worker* worker, struct config_file *cfg,
#ifdef SIGHUP
ub_thread_sig_unblock(SIGHUP);
#endif
+#ifdef SIGBREAK
+ ub_thread_sig_unblock(SIGBREAK);
+#endif
ub_thread_sig_unblock(SIGINT);
#ifdef SIGQUIT
ub_thread_sig_unblock(SIGQUIT);
@@ -1726,6 +1937,9 @@ worker_init(struct worker* worker, struct config_file *cfg,
|| !comm_signal_bind(worker->comsig, SIGQUIT)
#endif
|| !comm_signal_bind(worker->comsig, SIGTERM)
+#ifdef SIGBREAK
+ || !comm_signal_bind(worker->comsig, SIGBREAK)
+#endif
|| !comm_signal_bind(worker->comsig, SIGINT)) {
log_err("could not create signal handlers");
worker_delete(worker);