diff options
author | Cy Schubert <cy@FreeBSD.org> | 2022-06-08 14:43:13 +0000 |
---|---|---|
committer | Cy Schubert <cy@FreeBSD.org> | 2022-06-08 14:43:13 +0000 |
commit | 5f9f82264b91e041df7cba2406625146e7268ce4 (patch) | |
tree | ba7309ee547bf22115420277f45a3478aafb6397 /daemon/worker.c | |
parent | 3574dc0bd83e731bba79edc130c0569bf05f7af5 (diff) | |
download | src-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.c | 284 |
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); |