worker_handle_request(struct comm_point* c, void* arg, int error,
	struct comm_reply* repinfo)
{
	struct worker* worker = (struct worker*)arg;
	int ret;
	hashvalue_type h;
	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. */
	struct ub_packed_rrset_key* alias_rrset = NULL;
	struct reply_info* partial_rep = NULL;
	struct query_info* lookup_qinfo = &qinfo;
	struct query_info qinfo_tmp; /* placeholder for lookup_qinfo */
	struct respip_client_info* cinfo = NULL, cinfo_tmp;
	memset(&qinfo, 0, sizeof(qinfo));

	if((error != NETEVENT_NOERROR && error != NETEVENT_DONE)|| !repinfo) {
		/* some bad tcp query DNS formats give these error calls */
		verbose(VERB_ALGO, "handle request called with err=%d", error);
		return 0;
	}
#ifdef USE_DNSCRYPT
	repinfo->max_udp_size = worker->daemon->cfg->max_udp_size;
	if(!dnsc_handle_curved_request(worker->daemon->dnscenv, repinfo)) {
		worker->stats.num_query_dnscrypt_crypted_malformed++;
		return 0;
	}
	if(c->dnscrypt && !repinfo->is_dnscrypted) {
		char buf[LDNS_MAX_DOMAINLEN+1];
		/* Check if this is unencrypted and asking for certs */
		if(worker_check_request(c->buffer, worker) != 0) {
			verbose(VERB_ALGO,
				"dnscrypt: worker check request: bad query.");
			log_addr(VERB_CLIENT,"from",&repinfo->addr,
				repinfo->addrlen);
			comm_point_drop_reply(repinfo);
			return 0;
		}
		if(!query_info_parse(&qinfo, c->buffer)) {
			verbose(VERB_ALGO,
				"dnscrypt: worker parse request: formerror.");
			log_addr(VERB_CLIENT, "from", &repinfo->addr,
				repinfo->addrlen);
			comm_point_drop_reply(repinfo);
			return 0;
		}
		dname_str(qinfo.qname, buf);
		if(!(qinfo.qtype == LDNS_RR_TYPE_TXT &&
			strcasecmp(buf,
			worker->daemon->dnscenv->provider_name) == 0)) {
			verbose(VERB_ALGO,
				"dnscrypt: not TXT \"%s\". Received: %s \"%s\"",
				worker->daemon->dnscenv->provider_name,
				sldns_rr_descript(qinfo.qtype)->_name,
				buf);
			comm_point_drop_reply(repinfo);
			worker->stats.num_query_dnscrypt_cleartext++;
			return 0;
		}
		worker->stats.num_query_dnscrypt_cert++;
		sldns_buffer_rewind(c->buffer);
	} else if(c->dnscrypt && repinfo->is_dnscrypted) {
		worker->stats.num_query_dnscrypt_crypted++;
	}
#endif
#ifdef USE_DNSTAP
	/*
	 * sending src (client)/dst (local service) addresses over DNSTAP from incoming request handler
	 */
	if(worker->dtenv.log_client_query_messages) {
		log_addr(VERB_ALGO, "request from client", &repinfo->addr, repinfo->addrlen);
		log_addr(VERB_ALGO, "to local addr", (void*)repinfo->c->socket->addr->ai_addr, repinfo->c->socket->addr->ai_addrlen);
		dt_msg_send_client_query(&worker->dtenv, &repinfo->addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer);
	}
#endif
	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, acladdr,
		worker->env.cfg->ede)) != -1)
	{
		if(ret == 1)
			goto send_reply;
		return ret;
	}
	if((ret=worker_check_request(c->buffer, worker)) != 0) {
		verbose(VERB_ALGO, "worker check request: bad query.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		if(ret != -1) {
			LDNS_QR_SET(sldns_buffer_begin(c->buffer));
			LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
			return 1;
		}
		comm_point_drop_reply(repinfo);
		return 0;
	}

	worker->stats.num_queries++;

	/* check if this query should be dropped based on source ip rate limiting */
	if(!infra_ip_ratelimit_inc(worker->env.infra_cache, repinfo,
			*worker->env.now,
			worker->env.cfg->ip_ratelimit_backoff, c->buffer)) {
		/* See if we are passed through with slip factor */
		if(worker->env.cfg->ip_ratelimit_factor != 0 &&
			ub_random_max(worker->env.rnd,
						  worker->env.cfg->ip_ratelimit_factor) == 0) {

			char addrbuf[128];
			addr_to_str(&repinfo->addr, repinfo->addrlen,
						addrbuf, sizeof(addrbuf));
		  verbose(VERB_QUERY, "ip_ratelimit allowed through for ip address %s because of slip in ip_ratelimit_factor",
				  addrbuf);
		} else {
			worker->stats.num_queries_ip_ratelimited++;
			comm_point_drop_reply(repinfo);
			return 0;
		}
	}

	/* see if query is in the cache */
	if(!query_info_parse(&qinfo, c->buffer)) {
		verbose(VERB_ALGO, "worker parse request: formerror.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		memset(&qinfo, 0, sizeof(qinfo)); /* zero qinfo.qname */
		if(worker_err_ratelimit(worker, LDNS_RCODE_FORMERR) == -1) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		sldns_buffer_rewind(c->buffer);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), 
			LDNS_RCODE_FORMERR);
		goto send_reply;
	}
	if(worker->env.cfg->log_queries) {
		char ip[128];
		addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip));
		log_query_in(ip, qinfo.qname, qinfo.qtype, qinfo.qclass);
	}
	if(qinfo.qtype == LDNS_RR_TYPE_AXFR || 
		qinfo.qtype == LDNS_RR_TYPE_IXFR) {
		verbose(VERB_ALGO, "worker request: refused zone transfer.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		sldns_buffer_rewind(c->buffer);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), 
			LDNS_RCODE_REFUSED);
		if(worker->stats.extended) {
			worker->stats.qtype[qinfo.qtype]++;
		}
		goto send_reply;
	}
	if(qinfo.qtype == LDNS_RR_TYPE_OPT || 
		qinfo.qtype == LDNS_RR_TYPE_TSIG ||
		qinfo.qtype == LDNS_RR_TYPE_TKEY ||
		qinfo.qtype == LDNS_RR_TYPE_MAILA ||
		qinfo.qtype == LDNS_RR_TYPE_MAILB ||
		(qinfo.qtype >= 128 && qinfo.qtype <= 248)) {
		verbose(VERB_ALGO, "worker request: formerror for meta-type.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		if(worker_err_ratelimit(worker, LDNS_RCODE_FORMERR) == -1) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		sldns_buffer_rewind(c->buffer);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), 
			LDNS_RCODE_FORMERR);
		if(worker->stats.extended) {
			worker->stats.qtype[qinfo.qtype]++;
		}
		goto send_reply;
	}
	if((ret=parse_edns_from_query_pkt(c->buffer, &edns, worker->env.cfg, c,
					worker->scratchpad)) != 0) {
		struct edns_data reply_edns;
		verbose(VERB_ALGO, "worker parse edns: formerror.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		memset(&reply_edns, 0, sizeof(reply_edns));
		reply_edns.edns_present = 1;
		reply_edns.udp_size = EDNS_ADVERTISED_SIZE;
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
		error_encode(c->buffer, ret, &qinfo,
			*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
			sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns);
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(edns.edns_present) {
		if(edns.edns_version != 0) {
			edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4);
			edns.edns_version = EDNS_ADVERTISED_VERSION;
			edns.udp_size = EDNS_ADVERTISED_SIZE;
			edns.bits &= EDNS_DO;
			edns.opt_list_in = NULL;
			edns.opt_list_out = NULL;
			edns.opt_list_inplace_cb_out = NULL;
			edns.padding_block_size = 0;
			verbose(VERB_ALGO, "query with bad edns version.");
			log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
			error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo,
				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
				sldns_buffer_read_u16_at(c->buffer, 2), NULL);
			if(sldns_buffer_capacity(c->buffer) >=
			   sldns_buffer_limit(c->buffer)+calc_edns_field_size(&edns))
				attach_edns_record(c->buffer, &edns);
			regional_free_all(worker->scratchpad);
			goto send_reply;
		}
		if(edns.udp_size < NORMAL_UDP_SIZE &&
		   worker->daemon->cfg->harden_short_bufsize) {
			verbose(VERB_QUERY, "worker request: EDNS bufsize %d ignored",
				(int)edns.udp_size);
			log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
			edns.udp_size = NORMAL_UDP_SIZE;
		}
	}
	if(edns.udp_size > worker->daemon->cfg->max_udp_size &&
		c->type == comm_udp) {
		verbose(VERB_QUERY,
			"worker request: max UDP reply size modified"
			" (%d to max-udp-size)", (int)edns.udp_size);
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		edns.udp_size = worker->daemon->cfg->max_udp_size;
	}
	if(edns.udp_size < LDNS_HEADER_SIZE) {
		verbose(VERB_ALGO, "worker request: edns is too small.");
		log_addr(VERB_CLIENT, "from", &repinfo->addr, repinfo->addrlen);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_TC_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), 
			LDNS_RCODE_SERVFAIL);
		sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
		sldns_buffer_write_at(c->buffer, 4, 
			(uint8_t*)"\0\0\0\0\0\0\0\0", 8);
		sldns_buffer_flip(c->buffer);
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(worker->stats.extended)
		server_stats_insquery(&worker->stats, c, qinfo.qtype,
			qinfo.qclass, &edns, repinfo);
	if(c->type != comm_udp)
		edns.udp_size = 65535; /* max size for TCP replies */
	if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo,
		&edns, repinfo, c->buffer)) {
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) ==
		LDNS_PACKET_NOTIFY) {
		answer_notify(worker, &qinfo, &edns, c->buffer, repinfo);
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(local_zones_answer(worker->daemon->local_zones, &worker->env, &qinfo,
		&edns, c->buffer, worker->scratchpad, repinfo, acladdr->taglist,
		acladdr->taglen, acladdr->tag_actions,
		acladdr->tag_actions_size, acladdr->tag_datas,
		acladdr->tag_datas_size, worker->daemon->cfg->tagname,
		worker->daemon->cfg->num_tags, acladdr->view)) {
		regional_free_all(worker->scratchpad);
		if(sldns_buffer_limit(c->buffer) == 0) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		goto send_reply;
	}
	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,
		&rpz_passthru)) {
		regional_free_all(worker->scratchpad);
		if(sldns_buffer_limit(c->buffer) == 0) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		goto send_reply;
	}
	if(worker->env.auth_zones &&
		auth_zones_answer(worker->env.auth_zones, &worker->env,
		&qinfo, &edns, repinfo, c->buffer, worker->scratchpad)) {
		regional_free_all(worker->scratchpad);
		if(sldns_buffer_limit(c->buffer) == 0) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		/* set RA for everyone that can have recursion (based on
		 * access control list) */
		if(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer)) &&
		   acl != acl_deny_non_local && acl != acl_refuse_non_local)
			LDNS_RA_SET(sldns_buffer_begin(c->buffer));
		goto send_reply;
	}

	/* 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, acladdr,
		worker->env.cfg->ede)) != -1)
	{
		regional_free_all(worker->scratchpad);
		if(ret == 1)
			goto send_reply;
		return ret;
	}

	/* If this request does not have the recursion bit set, verify
	 * ACLs allow the recursion bit to be treated as set. */
	if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) &&
		acl == acl_allow_setrd ) {
		LDNS_RD_SET(sldns_buffer_begin(c->buffer));
	}

	/* If this request does not have the recursion bit set, verify
	 * 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), &edns);
		regional_free_all(worker->scratchpad);
		log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from",
			&repinfo->addr, repinfo->addrlen);

		goto send_reply;
	}

	/* If we've found a local alias, replace the qname with the alias
	 * target before resolving it. */
	if(qinfo.local_alias) {
		struct ub_packed_rrset_key* rrset = qinfo.local_alias->rrset;
		struct packed_rrset_data* d = rrset->entry.data;

		/* Sanity check: our current implementation only supports
		 * a single CNAME RRset as a local alias. */
		if(qinfo.local_alias->next ||
			rrset->rk.type != htons(LDNS_RR_TYPE_CNAME) ||
			d->count != 1) {
			log_err("assumption failure: unexpected local alias");
			regional_free_all(worker->scratchpad);
			return 0; /* drop it */
		}
		qinfo.qname = d->rr_data[0] + 2;
		qinfo.qname_len = d->rr_len[0] - 2;
	}

	/* If we may apply IP-based actions to the answer, build the client
	 * information.  As this can be expensive, skip it if there is
	 * absolutely no possibility of it. */
	if((worker->daemon->use_response_ip || worker->daemon->use_rpz) &&
		(qinfo.qtype == LDNS_RR_TYPE_A ||
		qinfo.qtype == LDNS_RR_TYPE_AAAA ||
		qinfo.qtype == LDNS_RR_TYPE_ANY)) {
		cinfo_tmp.taglist = acladdr->taglist;
		cinfo_tmp.taglen = acladdr->taglen;
		cinfo_tmp.tag_actions = acladdr->tag_actions;
		cinfo_tmp.tag_actions_size = acladdr->tag_actions_size;
		cinfo_tmp.tag_datas = acladdr->tag_datas;
		cinfo_tmp.tag_datas_size = acladdr->tag_datas_size;
		cinfo_tmp.view = acladdr->view;
		cinfo_tmp.respip_set = worker->daemon->respip_set;
		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
	 * each pass.  We should still pass the original qinfo to
	 * answer_from_cache(), however, since it's used to build the reply. */
	if(!edns_bypass_cache_stage(edns.opt_list_in, &worker->env)) {
		is_expired_answer = 0;
		is_secure_answer = 0;
		h = query_info_hash(lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2));
		if((e=slabhash_lookup(worker->env.msg_cache, h, lookup_qinfo, 0))) {
			struct reply_info* rep = (struct reply_info*)e->data;
			/* answer from cache - we have acquired a readlock on it */
			if(answer_from_cache(worker, &qinfo, cinfo, &need_drop,
				&is_expired_answer, &is_secure_answer,
				&alias_rrset, &partial_rep, rep,
				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
				sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
				&edns)) {
				/* prefetch it if the prefetch TTL expired.
				 * Note that if there is more than one pass
				 * its qname must be that used for cache
				 * lookup. */
				if((worker->env.cfg->prefetch &&
					*worker->env.now >= rep->prefetch_ttl) ||
					(worker->env.cfg->serve_expired &&
					*worker->env.now > rep->ttl)) {

					time_t leeway = rep->ttl - *worker->env.now;
					if(rep->ttl < *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),
						rpz_passthru,
						original_edns_list);
					if(!partial_rep) {
						rc = 0;
						regional_free_all(worker->scratchpad);
						goto send_reply_rc;
					}
				} else if(!partial_rep) {
					lock_rw_unlock(&e->lock);
					regional_free_all(worker->scratchpad);
					goto send_reply;
				} else {
					/* Note that we've already released the
					 * lock if we're here after prefetch. */
					lock_rw_unlock(&e->lock);
				}
				/* We've found a partial reply ending with an
				 * alias.  Replace the lookup qinfo for the
				 * alias target and lookup the cache again to
				 * (possibly) complete the reply.  As we're
				 * passing the "base" reply, there will be no
				 * more alias chasing. */
				memset(&qinfo_tmp, 0, sizeof(qinfo_tmp));
				get_cname_target(alias_rrset, &qinfo_tmp.qname,
					&qinfo_tmp.qname_len);
				if(!qinfo_tmp.qname) {
					log_err("unexpected: invalid answer alias");
					regional_free_all(worker->scratchpad);
					return 0; /* drop query */
				}
				qinfo_tmp.qtype = qinfo.qtype;
				qinfo_tmp.qclass = qinfo.qclass;
				lookup_qinfo = &qinfo_tmp;
				goto 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), 
				sldns_buffer_read_u16_at(c->buffer, 2), repinfo, 
				&edns)) {
				regional_free_all(worker->scratchpad);
				goto send_reply;
			}
			verbose(VERB_ALGO, "answer norec from cache -- "
				"need to validate or not primed");
		}
	}
	sldns_buffer_rewind(c->buffer);
	server_stats_querymiss(&worker->stats, worker);

	if(verbosity >= VERB_CLIENT) {
		if(c->type == comm_udp)
			log_addr(VERB_CLIENT, "udp request from",
				&repinfo->addr, repinfo->addrlen);
		else	log_addr(VERB_CLIENT, "tcp request from",
				&repinfo->addr, repinfo->addrlen);
	}

	/* 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),
		rpz_passthru);
	regional_free_all(worker->scratchpad);
	worker_mem_report(worker, NULL);
	return 0;

send_reply:
	rc = 1;
send_reply_rc:
	if(need_drop) {
		comm_point_drop_reply(repinfo);
		return 0;
	}
	if(is_expired_answer) {
		worker->stats.ans_expired++;
	}
	server_stats_insrcode(&worker->stats, c->buffer);
	if(worker->stats.extended) {
		if(is_secure_answer) worker->stats.ans_secure++;
	}
#ifdef USE_DNSTAP
	/*
	 * sending src (client)/dst (local service) addresses over DNSTAP from send_reply code label (when we serviced local zone for ex.)
	 */
	if(worker->dtenv.log_client_response_messages) {
		log_addr(VERB_ALGO, "from local addr", (void*)repinfo->c->socket->addr->ai_addr, repinfo->c->socket->addr->ai_addrlen);
                log_addr(VERB_ALGO, "response to client", &repinfo->addr, repinfo->addrlen);
		dt_msg_send_client_response(&worker->dtenv, &repinfo->addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer);
	}
#endif
	if(worker->env.cfg->log_replies)
	{
		struct timeval tv;
		memset(&tv, 0, sizeof(tv));
		if(qinfo.local_alias && qinfo.local_alias->rrset &&
			qinfo.local_alias->rrset->rk.dname) {
			/* log original qname, before the local alias was
			 * used to resolve that CNAME to something else */
			qinfo.qname = qinfo.local_alias->rrset->rk.dname;
			log_reply_info(NO_VERBOSE, &qinfo, &repinfo->addr, repinfo->addrlen,
				tv, 1, c->buffer);
		} else {
			log_reply_info(NO_VERBOSE, &qinfo, &repinfo->addr, repinfo->addrlen,
				tv, 1, c->buffer);
		}
	}
#ifdef USE_DNSCRYPT
	if(!dnsc_handle_uncurved_request(repinfo)) {
		return 0;
	}
#endif
	return rc;
}