Edit

IABSD.fr/src/sbin/unwind/resolver.c

Branch :

  • Show log

    Commit

  • Author : florian
    Date : 2025-09-15 08:43:51
    Hash : ac60b447
    Message : Disable aggressive-nsec when "force" is in use. When resolution of a domain is forced to a resolver type, the resolver might have an nsec chain in its cache that proofs the non-existence of the domain. With aggressive-nsec enabled (the default in unbound), the query will then not be forwarded and resolution fails, even if "accept bogus" is configured. For example, if one squats on the undelegated tld "foobar": force forwarder { foobar } and then typo's it as foobaa: foo. 86400 IN NSEC food. NS DS RRSIG NSEC Problem reported by, testing & OK tb Suggestion to turn off aggressive-nsec by otto

  • sbin/unwind/resolver.c
  • /*	$OpenBSD: resolver.c,v 1.175 2025/09/15 08:43:51 florian Exp $	*/
    
    
    /*
     * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
     * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org>
     * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
     * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/types.h>
    #include <sys/queue.h>
    #include <sys/socket.h>
    #include <sys/syslog.h>
    #include <sys/time.h>
    
    #include <net/route.h>
    
    #include <errno.h>
    #include <event.h>
    #include <imsg.h>
    #include <limits.h>
    #include <netdb.h>
    #include <asr.h>
    #include <pwd.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <tls.h>
    #include <unistd.h>
    
    #include "libunbound/config.h"
    #include "libunbound/libunbound/context.h"
    #include "libunbound/libunbound/libworker.h"
    #include "libunbound/libunbound/unbound.h"
    #include "libunbound/libunbound/unbound-event.h"
    #include "libunbound/services/cache/rrset.h"
    #include "libunbound/sldns/sbuffer.h"
    #include "libunbound/sldns/rrdef.h"
    #include "libunbound/sldns/pkthdr.h"
    #include "libunbound/sldns/wire2str.h"
    #include "libunbound/util/config_file.h"
    #include "libunbound/util/module.h"
    #include "libunbound/util/regional.h"
    #include "libunbound/util/storage/slabhash.h"
    #include "libunbound/validator/validator.h"
    #include "libunbound/validator/val_kcache.h"
    #include "libunbound/validator/val_neg.h"
    
    #include <openssl/crypto.h>
    
    #include "log.h"
    #include "frontend.h"
    #include "unwind.h"
    #include "resolver.h"
    
    #define	TLS_DEFAULT_CA_CERT_FILE	"/etc/ssl/cert.pem"
    #define	UB_LOG_VERBOSE			4
    #define	UB_LOG_BRIEF			0
    
    /* maximum size of a libunbound forwarder definition: IP@PORT#AUTHNAME */
    #define	FWD_MAX				(INET6_ADDRSTRLEN + NI_MAXHOST + 2 + 5)
    
    /*
     * The prefered resolver type can be this many ms slower than the next
     * best and still be picked
     */
    #define	PREF_RESOLVER_MEDIAN_SKEW	200		/* 200 ms */
    #define	NEXT_RES_MAX			2000		/* 2000 ms */
    
    #define	DOUBT_NXDOMAIN_SEC		(5 * 60)	/* 5 minutes */
    
    #define	RESOLVER_CHECK_SEC		1
    #define	RESOLVER_CHECK_MAXSEC		1024 /* ~17 minutes */
    #define	DECAY_PERIOD			60
    #define	DECAY_NOMINATOR			9
    #define	DECAY_DENOMINATOR		10
    
    #define	TRUST_ANCHOR_RETRY_INTERVAL	8640
    #define	TRUST_ANCHOR_QUERY_INTERVAL	43200
    
    /* in libworker_event_done_cb() enum sec_status gets mapped to 0, 1 and 2 */
    #define	INSECURE	0
    #define	BOGUS		1
    #define	SECURE		2
    
    #define	WKA1_FOUND	1
    #define	WKA2_FOUND	2
    
    struct uw_resolver {
    	struct event		 check_ev;
    	struct event		 free_ev;
    	struct ub_ctx		*ctx;
    	void			*asr_ctx;
    	struct timeval		 check_tv;
    	int			 ref_cnt;
    	int			 stop;
    	enum uw_resolver_state	 state;
    	enum uw_resolver_type	 type;
    	int			 check_running;
    	int64_t			 median;
    	int64_t			 histogram[nitems(histogram_limits)];
    	int64_t			 latest_histogram[nitems(histogram_limits)];
    };
    
    struct running_query {
    	TAILQ_ENTRY(running_query)	 entry;
    	struct query_imsg		*query_imsg;
    	struct event			 timer_ev;
    	struct timespec			 tp;
    	struct resolver_preference	 res_pref;
    	int				 next_resolver;
    	int				 running;
    };
    
    TAILQ_HEAD(, running_query)	 running_queries;
    
    typedef void (*resolve_cb_t)(struct uw_resolver *, void *, int, void *, int,
        int, char *);
    
    struct resolver_cb_data {
    	resolve_cb_t		 cb;
    	void			*data;
    	struct uw_resolver	*res;
    };
    
    __dead void		 resolver_shutdown(void);
    void			 resolver_sig_handler(int sig, short, void *);
    void			 resolver_dispatch_frontend(int, short, void *);
    void			 resolver_dispatch_main(int, short, void *);
    int			 sort_resolver_types(struct resolver_preference *);
    void			 setup_query(struct query_imsg *);
    struct running_query	*find_running_query(uint64_t);
    void			 try_resolver_timo(int, short, void *);
    int			 try_next_resolver(struct running_query *);
    
    int			 resolve(struct uw_resolver *, const char*, int, int,
    			     void*, resolve_cb_t);
    void			 resolve_done(struct uw_resolver *, void *, int, void *,
    			     int, int, char *);
    void			 ub_resolve_done(void *, int, void *, int, int, char *,
    			     int);
    void			 asr_resolve_done(struct asr_result *, void *);
    void			 new_resolver(enum uw_resolver_type,
    			     enum uw_resolver_state);
    struct uw_resolver	*create_resolver(enum uw_resolver_type);
    #ifdef UNIFIED_CACHE
    void			 setup_unified_caches(void);
    void			 set_unified_cache(struct uw_resolver *);
    #endif /* UNIFIED_CACHE */
    void			 free_resolver(struct uw_resolver *);
    void			 set_forwarders(struct uw_resolver *,
    			     struct uw_forwarder_head *, int);
    void			 resolver_check_timo(int, short, void *);
    void			 resolver_free_timo(int, short, void *);
    void			 check_resolver(struct uw_resolver *);
    void			 check_resolver_done(struct uw_resolver *, void *, int,
    			     void *, int, int, char *);
    void			 schedule_recheck_all_resolvers(void);
    int			 check_forwarders_changed(struct uw_forwarder_head *,
    			     struct uw_forwarder_head *);
    void			 replace_forwarders(struct uw_forwarder_head *,
    			     struct uw_forwarder_head *);
    void			 resolver_ref(struct uw_resolver *);
    void			 resolver_unref(struct uw_resolver *);
    int			 resolver_cmp(const void *, const void *);
    void			 restart_ub_resolvers(int);
    void			 show_status(pid_t);
    void			 show_autoconf(pid_t);
    void			 show_mem(pid_t);
    void			 send_resolver_info(struct uw_resolver *, pid_t);
    void			 trust_anchor_resolve(void);
    void			 trust_anchor_timo(int, short, void *);
    void			 trust_anchor_resolve_done(struct uw_resolver *, void *,
    			     int, void *, int, int, char *);
    void			 replace_autoconf_forwarders(struct
    			     imsg_rdns_proposal *);
    int			 force_tree_cmp(struct force_tree_entry *,
    			     struct force_tree_entry *);
    int			 find_force(struct force_tree *, char *,
    			     struct uw_resolver **);
    int64_t			 histogram_median(int64_t *);
    void			 decay_latest_histograms(int, short, void *);
    int			 running_query_cnt(void);
    int			*resolvers_to_restart(struct uw_conf *,
    			     struct uw_conf *);
    const char		*query_imsg2str(struct query_imsg *);
    char			*gen_resolv_conf(void);
    void			 check_dns64(void);
    void			 check_dns64_done(struct asr_result *, void *);
    int			 dns64_prefixlen(const struct in6_addr *,
    			     const uint8_t *);
    void			 add_dns64_prefix(const struct in6_addr *, int,
    			     struct dns64_prefix *, int, int);
    
    struct uw_conf			*resolver_conf;
    static struct imsgev		*iev_frontend;
    static struct imsgev		*iev_main;
    struct uw_forwarder_head	 autoconf_forwarder_list;
    struct uw_resolver		*resolvers[UW_RES_NONE];
    struct timespec			 last_network_change;
    
    struct event			 trust_anchor_timer;
    struct event			 decay_timer;
    
    static struct trust_anchor_head	 trust_anchors, new_trust_anchors;
    
    struct event_base		*ev_base;
    
    RB_GENERATE(force_tree, force_tree_entry, entry, force_tree_cmp)
    
    int				 val_id = -1;
    #ifdef UNIFIED_CACHE
    struct slabhash			*unified_msg_cache;
    struct rrset_cache		*unified_rrset_cache;
    struct key_cache		*unified_key_cache;
    struct val_neg_cache		*unified_neg_cache;
    #endif /* UNIFIED_CACHE */
    
    int				 dns64_present;
    int				 available_afs = HAVE_IPV4 | HAVE_IPV6;
    
    static const char * const	 forward_transparent_zones[] = {
    	/* RFC1918 */
    	"10.in-addr.arpa. transparent",
    	"16.172.in-addr.arpa. transparent",
    	"17.172.in-addr.arpa. transparent",
    	"18.172.in-addr.arpa. transparent",
    	"19.172.in-addr.arpa. transparent",
    	"20.172.in-addr.arpa. transparent",
    	"21.172.in-addr.arpa. transparent",
    	"22.172.in-addr.arpa. transparent",
    	"23.172.in-addr.arpa. transparent",
    	"24.172.in-addr.arpa. transparent",
    	"25.172.in-addr.arpa. transparent",
    	"26.172.in-addr.arpa. transparent",
    	"27.172.in-addr.arpa. transparent",
    	"28.172.in-addr.arpa. transparent",
    	"29.172.in-addr.arpa. transparent",
    	"30.172.in-addr.arpa. transparent",
    	"31.172.in-addr.arpa. transparent",
    	"168.192.in-addr.arpa. transparent",
    
    	/* RFC3330 */
    	"0.in-addr.arpa. transparent",
    	"254.169.in-addr.arpa. transparent",
    	"2.0.192.in-addr.arpa. transparent",
    	"100.51.198.in-addr.arpa. transparent",
    	"113.0.203.in-addr.arpa. transparent",
    	"255.255.255.255.in-addr.arpa. transparent",
    
    	/* RFC6598 */
    	"64.100.in-addr.arpa. transparent",
    	"65.100.in-addr.arpa. transparent",
    	"66.100.in-addr.arpa. transparent",
    	"67.100.in-addr.arpa. transparent",
    	"68.100.in-addr.arpa. transparent",
    	"69.100.in-addr.arpa. transparent",
    	"70.100.in-addr.arpa. transparent",
    	"71.100.in-addr.arpa. transparent",
    	"72.100.in-addr.arpa. transparent",
    	"73.100.in-addr.arpa. transparent",
    	"74.100.in-addr.arpa. transparent",
    	"75.100.in-addr.arpa. transparent",
    	"76.100.in-addr.arpa. transparent",
    	"77.100.in-addr.arpa. transparent",
    	"78.100.in-addr.arpa. transparent",
    	"79.100.in-addr.arpa. transparent",
    	"80.100.in-addr.arpa. transparent",
    	"81.100.in-addr.arpa. transparent",
    	"82.100.in-addr.arpa. transparent",
    	"83.100.in-addr.arpa. transparent",
    	"84.100.in-addr.arpa. transparent",
    	"85.100.in-addr.arpa. transparent",
    	"86.100.in-addr.arpa. transparent",
    	"87.100.in-addr.arpa. transparent",
    	"88.100.in-addr.arpa. transparent",
    	"89.100.in-addr.arpa. transparent",
    	"90.100.in-addr.arpa. transparent",
    	"91.100.in-addr.arpa. transparent",
    	"92.100.in-addr.arpa. transparent",
    	"93.100.in-addr.arpa. transparent",
    	"94.100.in-addr.arpa. transparent",
    	"95.100.in-addr.arpa. transparent",
    	"96.100.in-addr.arpa. transparent",
    	"97.100.in-addr.arpa. transparent",
    	"98.100.in-addr.arpa. transparent",
    	"99.100.in-addr.arpa. transparent",
    	"100.100.in-addr.arpa. transparent",
    	"101.100.in-addr.arpa. transparent",
    	"102.100.in-addr.arpa. transparent",
    	"103.100.in-addr.arpa. transparent",
    	"104.100.in-addr.arpa. transparent",
    	"105.100.in-addr.arpa. transparent",
    	"106.100.in-addr.arpa. transparent",
    	"107.100.in-addr.arpa. transparent",
    	"108.100.in-addr.arpa. transparent",
    	"109.100.in-addr.arpa. transparent",
    	"110.100.in-addr.arpa. transparent",
    	"111.100.in-addr.arpa. transparent",
    	"112.100.in-addr.arpa. transparent",
    	"113.100.in-addr.arpa. transparent",
    	"114.100.in-addr.arpa. transparent",
    	"115.100.in-addr.arpa. transparent",
    	"116.100.in-addr.arpa. transparent",
    	"117.100.in-addr.arpa. transparent",
    	"118.100.in-addr.arpa. transparent",
    	"119.100.in-addr.arpa. transparent",
    	"120.100.in-addr.arpa. transparent",
    	"121.100.in-addr.arpa. transparent",
    	"122.100.in-addr.arpa. transparent",
    	"123.100.in-addr.arpa. transparent",
    	"124.100.in-addr.arpa. transparent",
    	"125.100.in-addr.arpa. transparent",
    	"126.100.in-addr.arpa. transparent",
    	"127.100.in-addr.arpa. transparent",
    
    	/* RFC4291 */
    	"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0."
    	"ip6.arpa. transparent",
    
    	/* RFC4193 */
    	"D.F.ip6.arpa. transparent",
    
    	/* RFC4291 */
    	"8.E.F.ip6.arpa. transparent",
    	"9.E.F.ip6.arpa. transparent",
    	"A.E.F.ip6.arpa. transparent",
    	"B.E.F.ip6.arpa. transparent",
    
    	/* RFC3849 */
    	"8.B.D.0.1.0.0.2.ip6.arpa. transparent",
    
    	/* RFC8375 */
    	"home.arpa. transparent",
    };
    
    const char	 bogus_past[]	= "validation failure <. NS IN>: signature "
    				  "expired";
    const char	 bogus_future[]	= "validation failure <. NS IN>: signature "
    				  "before inception date";
    
    void
    resolver_sig_handler(int sig, short event, void *arg)
    {
    	/*
    	 * Normal signal handler rules don't apply because libevent
    	 * decouples for us.
    	 */
    
    	switch (sig) {
    	case SIGINT:
    	case SIGTERM:
    		resolver_shutdown();
    	default:
    		fatalx("unexpected signal");
    	}
    }
    
    void
    resolver(int debug, int verbose)
    {
    	struct event		 ev_sigint, ev_sigterm;
    	struct passwd		*pw;
    	struct timeval		 tv = {DECAY_PERIOD, 0};
    	struct alloc_cache	 cache_alloc_test;
    
    	resolver_conf = config_new_empty();
    
    	log_init(debug, LOG_DAEMON);
    	log_setverbose(verbose);
    
    	if ((pw = getpwnam(UNWIND_USER)) == NULL)
    		fatal("getpwnam");
    
    	setproctitle("%s", "resolver");
    	log_procinit("resolver");
    
    	if (setgroups(1, &pw->pw_gid) ||
    	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
    	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
    		fatal("can't drop privileges");
    
    	if (unveil(TLS_DEFAULT_CA_CERT_FILE, "r") == -1)
    		fatal("unveil %s", TLS_DEFAULT_CA_CERT_FILE);
    
    	if (pledge("stdio inet dns rpath recvfd", NULL) == -1)
    		fatal("pledge");
    
    	ev_base = event_init();
    
    	/* Setup signal handler(s). */
    	signal_set(&ev_sigint, SIGINT, resolver_sig_handler, NULL);
    	signal_set(&ev_sigterm, SIGTERM, resolver_sig_handler, NULL);
    	signal_add(&ev_sigint, NULL);
    	signal_add(&ev_sigterm, NULL);
    	signal(SIGPIPE, SIG_IGN);
    	signal(SIGHUP, SIG_IGN);
    
    	/* Setup pipe and event handler to the main process. */
    	if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
    		fatal(NULL);
    
    	if (imsgbuf_init(&iev_main->ibuf, 3) == -1)
    		fatal(NULL);
    	imsgbuf_allow_fdpass(&iev_main->ibuf);
    	iev_main->handler = resolver_dispatch_main;
    
    	/* Setup event handlers. */
    	iev_main->events = EV_READ;
    	event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
    	    iev_main->handler, iev_main);
    	event_add(&iev_main->ev, NULL);
    
    	evtimer_set(&trust_anchor_timer, trust_anchor_timo, NULL);
    	evtimer_set(&decay_timer, decay_latest_histograms, NULL);
    	evtimer_add(&decay_timer, &tv);
    
    	clock_gettime(CLOCK_MONOTONIC, &last_network_change);
    
    	alloc_init(&cache_alloc_test, NULL, 0);
    	if (cache_alloc_test.max_reg_blocks != 10)
    		fatalx("local libunbound/util/alloc.c diff lost");
    	alloc_clear(&cache_alloc_test);
    
    #ifdef UNIFIED_CACHE
    	setup_unified_caches();
    #endif /* UNIFIED_CACHE */
    
    	TAILQ_INIT(&autoconf_forwarder_list);
    	TAILQ_INIT(&trust_anchors);
    	TAILQ_INIT(&new_trust_anchors);
    	TAILQ_INIT(&running_queries);
    
    	event_dispatch();
    
    	resolver_shutdown();
    }
    
    __dead void
    resolver_shutdown(void)
    {
    	/* Close pipes. */
    	imsgbuf_clear(&iev_frontend->ibuf);
    	close(iev_frontend->ibuf.fd);
    	imsgbuf_clear(&iev_main->ibuf);
    	close(iev_main->ibuf.fd);
    
    	config_clear(resolver_conf);
    
    	free(iev_frontend);
    	free(iev_main);
    
    	log_info("resolver exiting");
    	exit(0);
    }
    
    int
    resolver_imsg_compose_main(int type, pid_t pid, void *data, uint16_t datalen)
    {
    	return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen));
    }
    
    int
    resolver_imsg_compose_frontend(int type, pid_t pid, void *data,
        uint16_t datalen)
    {
    	return (imsg_compose_event(iev_frontend, type, 0, pid, -1,
    	    data, datalen));
    }
    
    void
    resolver_dispatch_frontend(int fd, short event, void *bula)
    {
    	struct imsgev		*iev = bula;
    	struct imsgbuf		*ibuf;
    	struct imsg		 imsg;
    	struct query_imsg	*query_imsg;
    	ssize_t			 n;
    	int			 shut = 0, verbose, i, new_available_afs;
    	char			*ta;
    
    	ibuf = &iev->ibuf;
    
    	if (event & EV_READ) {
    		if ((n = imsgbuf_read(ibuf)) == -1)
    			fatal("imsgbuf_read error");
    		if (n == 0)	/* Connection closed. */
    			shut = 1;
    	}
    	if (event & EV_WRITE) {
    		if (imsgbuf_write(ibuf) == -1) {
    			if (errno == EPIPE)	/* Connection closed. */
    				shut = 1;
    			else
    				fatal("imsgbuf_write");
    		}
    	}
    
    	for (;;) {
    		if ((n = imsg_get(ibuf, &imsg)) == -1)
    			fatal("%s: imsg_get error", __func__);
    		if (n == 0)	/* No more messages. */
    			break;
    
    		switch (imsg.hdr.type) {
    		case IMSG_CTL_LOG_VERBOSE:
    			if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
    				fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: "
    				    "%lu", __func__,
    				    IMSG_DATA_SIZE(imsg));
    			memcpy(&verbose, imsg.data, sizeof(verbose));
    			if (log_getdebug() && (log_getverbose() & OPT_VERBOSE3)
    			    != (verbose & OPT_VERBOSE3))
    				restart_ub_resolvers(0);
    			log_setverbose(verbose);
    			break;
    		case IMSG_QUERY:
    			if (IMSG_DATA_SIZE(imsg) != sizeof(*query_imsg))
    				fatalx("%s: IMSG_QUERY wrong length: %lu",
    				    __func__, IMSG_DATA_SIZE(imsg));
    			if ((query_imsg = malloc(sizeof(*query_imsg))) ==
    			    NULL) {
    				log_warn("cannot allocate query");
    				break;
    			}
    			memcpy(query_imsg, imsg.data, sizeof(*query_imsg));
    			if (query_imsg->qname[NI_MAXHOST - 1] != '\0')
    				fatalx("%s: IMSG_QUERY invalid", __func__);
    
    			setup_query(query_imsg);
    			break;
    		case IMSG_CTL_STATUS:
    			if (IMSG_DATA_SIZE(imsg) != 0)
    				fatalx("%s: IMSG_CTL_STATUS wrong length: %lu",
    				    __func__, IMSG_DATA_SIZE(imsg));
    			show_status(imsg.hdr.pid);
    			break;
    		case IMSG_CTL_AUTOCONF:
    			if (IMSG_DATA_SIZE(imsg) != 0)
    				fatalx("%s: IMSG_CTL_AUTOCONF wrong length: "
    				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
    			show_autoconf(imsg.hdr.pid);
    			break;
    		case IMSG_CTL_MEM:
    			if (IMSG_DATA_SIZE(imsg) != 0)
    				fatalx("%s: IMSG_CTL_AUTOCONF wrong length: "
    				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
    			show_mem(imsg.hdr.pid);
    			break;
    		case IMSG_NEW_TA:
    			if (((char *)imsg.data)[IMSG_DATA_SIZE(imsg) - 1] !=
    			    '\0')
    				fatalx("Invalid trust anchor");
    			ta = imsg.data;
    			add_new_ta(&new_trust_anchors, ta);
    			break;
    		case IMSG_NEW_TAS_ABORT:
    			free_tas(&new_trust_anchors);
    			break;
    		case IMSG_NEW_TAS_DONE:
    			if (merge_tas(&new_trust_anchors, &trust_anchors))
    				restart_ub_resolvers(1);
    			break;
    		case IMSG_NETWORK_CHANGED:
    			clock_gettime(CLOCK_MONOTONIC, &last_network_change);
    			schedule_recheck_all_resolvers();
    			for (i = 0; i < UW_RES_NONE; i++) {
    				if (resolvers[i] == NULL)
    					continue;
    				memset(resolvers[i]->latest_histogram, 0,
    				    sizeof(resolvers[i]->latest_histogram));
    				resolvers[i]->median = histogram_median(
    				    resolvers[i]->latest_histogram);
    			}
    
    			break;
    		case IMSG_REPLACE_DNS:
    			if (IMSG_DATA_SIZE(imsg) !=
    			    sizeof(struct imsg_rdns_proposal))
    				fatalx("%s: IMSG_ADD_DNS wrong length: %lu",
    				    __func__, IMSG_DATA_SIZE(imsg));
    			replace_autoconf_forwarders((struct
    			    imsg_rdns_proposal *)imsg.data);
    			break;
    		case IMSG_CHANGE_AFS:
    			if (IMSG_DATA_SIZE(imsg) !=
    			    sizeof(new_available_afs))
    				fatalx("%s: IMSG_CHANGE_AFS wrong length: %lu",
    				    __func__, IMSG_DATA_SIZE(imsg));
    			memcpy(&new_available_afs, imsg.data,
    			    sizeof(new_available_afs));
    			if (new_available_afs != available_afs) {
    				available_afs = new_available_afs;
    				restart_ub_resolvers(1);
    			}
    			break;
    		default:
    			log_debug("%s: unexpected imsg %d", __func__,
    			    imsg.hdr.type);
    			break;
    		}
    		imsg_free(&imsg);
    	}
    	if (!shut)
    		imsg_event_add(iev);
    	else {
    		/* This pipe is dead. Remove its event handler. */
    		event_del(&iev->ev);
    		event_loopexit(NULL);
    	}
    }
    
    void
    resolver_dispatch_main(int fd, short event, void *bula)
    {
    	static struct uw_conf	*nconf;
    	struct imsg		 imsg;
    	struct imsgev		*iev = bula;
    	struct imsgbuf		*ibuf;
    	ssize_t			 n;
    	int			 shut = 0, i, *restart;
    
    	ibuf = &iev->ibuf;
    
    	if (event & EV_READ) {
    		if ((n = imsgbuf_read(ibuf)) == -1)
    			fatal("imsgbuf_read error");
    		if (n == 0)	/* Connection closed. */
    			shut = 1;
    	}
    	if (event & EV_WRITE) {
    		if (imsgbuf_write(ibuf) == -1) {
    			if (errno == EPIPE)	/* Connection closed. */
    				shut = 1;
    			else
    				fatal("imsgbuf_write");
    		}
    	}
    
    	for (;;) {
    		if ((n = imsg_get(ibuf, &imsg)) == -1)
    			fatal("%s: imsg_get error", __func__);
    		if (n == 0)	/* No more messages. */
    			break;
    
    		switch (imsg.hdr.type) {
    		case IMSG_SOCKET_IPC_FRONTEND:
    			/*
    			 * Setup pipe and event handler to the frontend
    			 * process.
    			 */
    			if (iev_frontend)
    				fatalx("%s: received unexpected imsg fd "
    				    "to resolver", __func__);
    
    			if ((fd = imsg_get_fd(&imsg)) == -1)
    				fatalx("%s: expected to receive imsg fd to "
    				   "resolver but didn't receive any", __func__);
    
    			iev_frontend = malloc(sizeof(struct imsgev));
    			if (iev_frontend == NULL)
    				fatal(NULL);
    
    			if (imsgbuf_init(&iev_frontend->ibuf, fd) == -1)
    				fatal(NULL);
    			iev_frontend->handler = resolver_dispatch_frontend;
    			iev_frontend->events = EV_READ;
    
    			event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
    			iev_frontend->events, iev_frontend->handler,
    			    iev_frontend);
    			event_add(&iev_frontend->ev, NULL);
    			break;
    
    		case IMSG_STARTUP:
    			if (pledge("stdio inet dns rpath", NULL) == -1)
    				fatal("pledge");
    			break;
    		case IMSG_RECONF_CONF:
    		case IMSG_RECONF_BLOCKLIST_FILE:
    		case IMSG_RECONF_FORWARDER:
    		case IMSG_RECONF_DOT_FORWARDER:
    		case IMSG_RECONF_FORCE:
    			imsg_receive_config(&imsg, &nconf);
    			break;
    		case IMSG_RECONF_END:
    			if (nconf == NULL)
    				fatalx("%s: IMSG_RECONF_END without "
    				    "IMSG_RECONF_CONF", __func__);
    			restart = resolvers_to_restart(resolver_conf, nconf);
    			merge_config(resolver_conf, nconf);
    			nconf = NULL;
    			for (i = 0; i < UW_RES_NONE; i++)
    				if (restart[i])
    					new_resolver(i, UNKNOWN);
    			break;
    		default:
    			log_debug("%s: unexpected imsg %d", __func__,
    			    imsg.hdr.type);
    			break;
    		}
    		imsg_free(&imsg);
    	}
    	if (!shut)
    		imsg_event_add(iev);
    	else {
    		/* This pipe is dead. Remove its event handler. */
    		event_del(&iev->ev);
    		event_loopexit(NULL);
    	}
    }
    
    int
    sort_resolver_types(struct resolver_preference *dst)
    {
    	memcpy(dst, &resolver_conf->res_pref, sizeof(*dst));
    
    	/*
    	 * Sort by resolver quality, validating > resolving etc.
    	 * mergesort is stable and keeps the configured preference order
    	 */
    	return mergesort(dst->types, dst->len, sizeof(dst->types[0]),
    	    resolver_cmp);
    }
    
    void
    setup_query(struct query_imsg *query_imsg)
    {
    	struct running_query	*rq;
    	struct uw_resolver	*res;
    
    	if (find_running_query(query_imsg->id) != NULL) {
    		free(query_imsg);
    		return;
    	}
    
    	if ((rq = calloc(1, sizeof(*rq))) == NULL) {
    		log_warnx(NULL);
    		free(query_imsg);
    		return;
    	}
    
    	clock_gettime(CLOCK_MONOTONIC, &rq->tp);
    	rq->query_imsg = query_imsg;
    	rq->next_resolver = 0;
    
    	find_force(&resolver_conf->force, query_imsg->qname, &res);
    
    	if (res != NULL && res->state != DEAD && res->state != UNKNOWN) {
    		rq->res_pref.len = 1;
    		rq->res_pref.types[0] = res->type;
    	} else if (sort_resolver_types(&rq->res_pref) == -1) {
    		log_warn("mergesort");
    		free(rq->query_imsg);
    		free(rq);
    		return;
    	}
    
    	evtimer_set(&rq->timer_ev, try_resolver_timo, rq);
    
    	TAILQ_INSERT_TAIL(&running_queries, rq, entry);
    	try_next_resolver(rq);
    }
    
    struct running_query *
    find_running_query(uint64_t id)
    {
    	struct running_query	*rq;
    
    	TAILQ_FOREACH(rq, &running_queries, entry) {
    		if (rq->query_imsg->id == id)
    			return rq;
    	}
    	return NULL;
    }
    
    void
    try_resolver_timo(int fd, short events, void *arg)
    {
    	struct running_query	*rq = arg;
    
    	try_next_resolver(rq);
    }
    
    int
    try_next_resolver(struct running_query *rq)
    {
    	struct uw_resolver	*res = NULL;
    	struct query_imsg	*query_imsg = NULL;
    	struct timespec		 tp, elapsed;
    	struct timeval		 tv = {0, 0};
    	int64_t			 ms;
    	int			 i;
    
    	while(rq->next_resolver < rq->res_pref.len &&
    	    ((res = resolvers[rq->res_pref.types[rq->next_resolver]]) == NULL ||
    	    res->state == DEAD || res->state == UNKNOWN))
    		rq->next_resolver++;
    
    	if (res == NULL) {
    		evtimer_del(&rq->timer_ev); /* we are not going to find one */
    		log_debug("%s: could not find (any more) working resolvers",
    		    __func__);
    		goto err;
    	}
    
    	rq->next_resolver++;
    	clock_gettime(CLOCK_MONOTONIC, &tp);
    	timespecsub(&tp, &rq->tp, &elapsed);
    	ms = elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
    
    	log_debug("%s[+%lldms]: %s[%s] %s", __func__, ms,
    	    uw_resolver_type_str[res->type], uw_resolver_state_str[res->state],
    	    query_imsg2str(rq->query_imsg));
    
    	if ((query_imsg = malloc(sizeof(*query_imsg))) == NULL) {
    		log_warnx("%s", __func__);
    		goto err;
    	}
    	memcpy(query_imsg, rq->query_imsg, sizeof(*query_imsg));
    	clock_gettime(CLOCK_MONOTONIC, &query_imsg->tp);
    
    	ms = res->median;
    	if (ms > NEXT_RES_MAX)
    		ms = NEXT_RES_MAX;
    
    	/* skip over unavailable resolvers in preferences */
    	for (i = 0; i < resolver_conf->res_pref.len &&
    		 resolvers[resolver_conf->res_pref.types[i]] == NULL; i++)
    		;
    	if (res->type == resolver_conf->res_pref.types[i])
    		tv.tv_usec = 1000 * (PREF_RESOLVER_MEDIAN_SKEW + ms);
    	else
    		tv.tv_usec = 1000 * ms;
    
    	while (tv.tv_usec >= 1000000) {
    		tv.tv_sec++;
    		tv.tv_usec -= 1000000;
    	}
    	evtimer_add(&rq->timer_ev, &tv);
    
    	rq->running++;
    	if (resolve(res, query_imsg->qname, query_imsg->t,
    	    query_imsg->c, query_imsg, resolve_done) != 0) {
    		rq->running--;
    		goto err;
    	}
    
    	return 0;
    
     err:
    	free(query_imsg);
    	if (rq->running == 0) {
    		TAILQ_REMOVE(&running_queries, rq, entry);
    		evtimer_del(&rq->timer_ev);
    		free(rq->query_imsg);
    		free(rq);
    	}
    	return 1;
    }
    
    int
    resolve(struct uw_resolver *res, const char* name, int rrtype, int rrclass,
        void *mydata, resolve_cb_t cb)
    {
    	struct resolver_cb_data	*cb_data = NULL;
    	struct asr_query	*aq = NULL;
    	int			 err;
    
    	resolver_ref(res);
    
    	if ((cb_data = malloc(sizeof(*cb_data))) == NULL)
    		goto err;
    	cb_data->cb = cb;
    	cb_data->data = mydata;
    	cb_data->res = res;
    
    	switch(res->type) {
    	case UW_RES_ASR:
    		if ((aq = res_query_async(name, rrclass, rrtype, res->asr_ctx))
    		    == NULL) {
    			log_warn("%s: res_query_async", __func__);
    			goto err;
    		}
    		if (event_asr_run(aq, asr_resolve_done, cb_data) == NULL) {
    			log_warn("%s: res_query_async", __func__);
    			goto err;
    		}
    		break;
    	case UW_RES_RECURSOR:
    	case UW_RES_AUTOCONF:
    	case UW_RES_ODOT_AUTOCONF:
    	case UW_RES_FORWARDER:
    	case UW_RES_ODOT_FORWARDER:
    	case UW_RES_DOT:
    		if ((err = ub_resolve_event(res->ctx, name,  rrtype, rrclass,
    		    cb_data, ub_resolve_done, NULL)) != 0) {
    			log_warn("%s: ub_resolve_event: err: %d, %s", __func__,
    			    err, ub_strerror(err));
    			goto err;
    		}
    		break;
    	default:
    		fatalx("unknown resolver type %d", res->type);
    		break;
    	}
    
    	return 0;
     err:
    	free(cb_data);
    	free(aq);
    	resolver_unref(res);
    	return 1;
    }
    
    void
    resolve_done(struct uw_resolver *res, void *arg, int rcode,
        void *answer_packet, int answer_len, int sec, char *why_bogus)
    {
    	struct uw_resolver	*tmp_res;
    	struct ub_result	*result = NULL;
    	sldns_buffer		*buf = NULL;
    	struct regional		*region = NULL;
    	struct query_imsg	*query_imsg;
    	struct answer_header	*answer_header;
    	struct running_query	*rq;
    	struct timespec		 tp, elapsed;
    	int64_t			 ms;
    	size_t			 i;
    	int			 running_res, asr_pref_pos, force_acceptbogus;
    	char			*str;
    	char			 rcode_buf[16];
    	uint8_t			*p, *data;
    	uint8_t			 answer_imsg[MAX_IMSGSIZE - IMSG_HEADER_SIZE];
    
    	clock_gettime(CLOCK_MONOTONIC, &tp);
    
    	query_imsg = (struct query_imsg *)arg;
    
    	answer_header = (struct answer_header *)answer_imsg;
    	data = answer_imsg + sizeof(*answer_header);
    	answer_header->id = query_imsg->id;
    	answer_header->srvfail = 0;
    	answer_header->answer_len = 0;
    
    	timespecsub(&tp, &query_imsg->tp, &elapsed);
    
    	ms = elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
    
    	for (i = 0; i < nitems(histogram_limits); i++) {
    		if (ms < histogram_limits[i])
    			break;
    	}
    	if (i == nitems(histogram_limits))
    		log_debug("histogram bucket error");
    	else {
    		res->histogram[i]++;
    		/* latest_histogram is in units of 1000 to avoid rounding
    		   down when decaying */
    		res->latest_histogram[i] += 1000;
    		res->median = histogram_median(res->latest_histogram);
    	}
    
    	if ((rq = find_running_query(query_imsg->id)) == NULL)
    		goto out;
    
    	running_res = --rq->running;
    
    	if (rcode == LDNS_RCODE_SERVFAIL) {
    		if (res->stop != 1)
    			check_resolver(res);
    		goto servfail;
    	}
    
    	if (answer_len < LDNS_HEADER_SIZE) {
    		log_warnx("bad packet: too short");
    		goto servfail;
    	}
    
    	if (answer_len > UINT16_MAX) {
    		log_warnx("bad packet: too large: %d - %s", answer_len,
    		    query_imsg2str(query_imsg));
    		goto servfail;
    	}
    	answer_header->answer_len = answer_len;
    
    	if ((result = calloc(1, sizeof(*result))) == NULL)
    		goto servfail;
    	if ((buf = sldns_buffer_new(answer_len)) == NULL)
    		goto servfail;
    	if ((region = regional_create()) == NULL)
    		goto servfail;
    
    	result->rcode = LDNS_RCODE_SERVFAIL;
    
    	sldns_buffer_clear(buf);
    	sldns_buffer_write(buf, answer_packet, answer_len);
    	sldns_buffer_flip(buf);
    	libworker_enter_result(result, buf, region, sec);
    	result->answer_packet = NULL;
    	result->answer_len = 0;
    
    	sldns_wire2str_rcode_buf(result->rcode, rcode_buf, sizeof(rcode_buf));
    	log_debug("%s[%s]: %s rcode: %s[%d], elapsed: %lldms, running: %d",
    	    __func__, uw_resolver_type_str[res->type],
    	    query_imsg2str(query_imsg), rcode_buf, result->rcode, ms,
    	    running_query_cnt());
    
    	force_acceptbogus = find_force(&resolver_conf->force, query_imsg->qname,
    	    &tmp_res);
    	if (tmp_res != NULL && tmp_res->type != res->type)
    		force_acceptbogus = 0;
    
    	timespecsub(&tp, &last_network_change, &elapsed);
    	if (sec != SECURE && elapsed.tv_sec < DOUBT_NXDOMAIN_SEC &&
    	    !force_acceptbogus && res->type != UW_RES_ASR &&
    	    (result->rcode == LDNS_RCODE_NXDOMAIN || sec == BOGUS)) {
    		/*
    		 * Doubt NXDOMAIN or BOGUS if we just switched networks, we
    		 * might be behind a captive portal.
    		 */
    		log_debug("%s: doubt NXDOMAIN or BOGUS from %s, network change"
    		    " %llds ago", __func__, uw_resolver_type_str[res->type],
    		    elapsed.tv_sec);
    
    		/* search for ASR */
    		asr_pref_pos = -1;
    		for (i = 0; i < (size_t)rq->res_pref.len; i++)
    			if (rq->res_pref.types[i] == UW_RES_ASR) {
    				asr_pref_pos = i;
    				break;
    			}
    
    		if (asr_pref_pos != -1 && resolvers[UW_RES_ASR] != NULL) {
    			/* go to ASR if not yet scheduled */
    			if (asr_pref_pos >= rq->next_resolver) {
    				rq->next_resolver = asr_pref_pos;
    				try_next_resolver(rq);
    			}
    			goto out;
    		}
    		log_debug("%s: using NXDOMAIN or BOGUS, couldn't find working "
    		    "ASR", __func__);
    	}
    
    	if (log_getverbose() & OPT_VERBOSE2 && (str =
    	    sldns_wire2str_pkt(answer_packet, answer_len)) != NULL) {
    		log_debug("%s", str);
    		free(str);
    	}
    
    	if (result->rcode == LDNS_RCODE_SERVFAIL)
    		goto servfail;
    
    	if (sec == SECURE && res->state != VALIDATING && res->stop != -1)
    		check_resolver(res);
    
    	if (res->state == VALIDATING && sec == BOGUS) {
    		answer_header->bogus = !force_acceptbogus;
    		if (answer_header->bogus && why_bogus != NULL)
    			log_warnx("%s", why_bogus);
    	} else
    		answer_header->bogus = 0;
    
    	p = answer_packet;
    	do {
    		int len;
    
    		if ((size_t)answer_len > sizeof(answer_imsg) -
    		    sizeof(*answer_header))
    			len = sizeof(answer_imsg) - sizeof(*answer_header);
    		else
    			len = answer_len;
    		memcpy(data, p, len);
    		if (resolver_imsg_compose_frontend(IMSG_ANSWER, 0,
    		    &answer_imsg, sizeof(*answer_header) + len) == -1)
    			fatalx("IMSG_ANSWER failed for \"%s\"",
    			    query_imsg2str(query_imsg));
    		answer_len -= len;
    		p += len;
    	} while (answer_len > 0);
    
    	TAILQ_REMOVE(&running_queries, rq, entry);
    	evtimer_del(&rq->timer_ev);
    	free(rq->query_imsg);
    	free(rq);
    	goto out;
    
     servfail:
    	/* try_next_resolver() might free rq */
    	if (try_next_resolver(rq) != 0 && running_res == 0) {
    		/* we are the last one, send SERVFAIL */
    		answer_header->srvfail = 1;
    		resolver_imsg_compose_frontend(IMSG_ANSWER, 0,
    		    answer_imsg, sizeof(*answer_header));
    	}
     out:
    	free(query_imsg);
    	sldns_buffer_free(buf);
    	regional_destroy(region);
    	ub_resolve_free(result);
    }
    
    void
    new_resolver(enum uw_resolver_type type, enum uw_resolver_state state)
    {
    	free_resolver(resolvers[type]);
    	resolvers[type] = NULL;
    
    	if (!resolver_conf->enabled_resolvers[type])
    		return;
    
    	switch (type) {
    	case UW_RES_ASR:
    	case UW_RES_AUTOCONF:
    	case UW_RES_ODOT_AUTOCONF:
    		if (TAILQ_EMPTY(&autoconf_forwarder_list))
    			return;
    		break;
    	case UW_RES_RECURSOR:
    		break;
    	case UW_RES_FORWARDER:
    	case UW_RES_ODOT_FORWARDER:
    		if (TAILQ_EMPTY(&resolver_conf->uw_forwarder_list))
    			return;
    		break;
    	case UW_RES_DOT:
    		if (TAILQ_EMPTY(&resolver_conf->uw_dot_forwarder_list))
    			return;
    		break;
    	case UW_RES_NONE:
    		fatalx("cannot create UW_RES_NONE resolver");
    	}
    
    	switch (type) {
    	case UW_RES_RECURSOR:
    	case UW_RES_AUTOCONF:
    	case UW_RES_ODOT_AUTOCONF:
    	case UW_RES_FORWARDER:
    	case UW_RES_ODOT_FORWARDER:
    	case UW_RES_DOT:
    		if (TAILQ_EMPTY(&trust_anchors))
    			return;
    		break;
    	case UW_RES_ASR:
    		break;
    	case UW_RES_NONE:
    		fatalx("cannot create UW_RES_NONE resolver");
    	}
    
    	if ((resolvers[type] = create_resolver(type)) == NULL)
    		return;
    
    	switch (state) {
    	case DEAD:
    	case UNKNOWN:
    		check_resolver(resolvers[type]);
    		break;
    	case VALIDATING:
    #ifdef UNIFIED_CACHE
    		set_unified_cache(resolvers[type]);
    #endif /* UNIFIED_CACHE */
    		/* FALLTHROUGH */
    	case RESOLVING:
    		resolvers[type]->state = state;
    		if (type == UW_RES_ASR)
    			check_dns64();
    		break;
    	}
    }
    
    #ifdef UNIFIED_CACHE
    void
    set_unified_cache(struct uw_resolver *res)
    {
    	if (res == NULL || res->ctx == NULL)
    		return;
    
    	if (res->ctx->env->msg_cache != NULL) {
    		/* XXX we are currently not using this */
    		if (res->ctx->env->msg_cache != unified_msg_cache ||
    		    res->ctx->env->rrset_cache != unified_rrset_cache ||
    		    res->ctx->env->key_cache != unified_key_cache ||
    		    res->ctx->env->neg_cache != unified_neg_cache)
    			fatalx("wrong unified cache set on resolver");
    		else
    			/* we are upgrading from UNKNOWN back to VALIDATING */
    			return;
    	}
    
    	res->ctx->env->msg_cache = unified_msg_cache;
    	res->ctx->env->rrset_cache = unified_rrset_cache;
    	res->ctx->env->key_cache = unified_key_cache;
    	res->ctx->env->neg_cache = unified_neg_cache;
    
    	context_finalize(res->ctx);
    
    	if (res->ctx->env->msg_cache != unified_msg_cache ||
    	    res->ctx->env->rrset_cache != unified_rrset_cache ||
    	    res->ctx->env->key_cache != unified_key_cache ||
    	    res->ctx->env->neg_cache != unified_neg_cache)
    		fatalx("failed to set unified caches, libunbound/validator/"
    		    "validator.c diff lost");
    }
    #endif /* UNIFIED_CACHE */
    
    static const struct {
    	const char *name;
    	const char *value;
    } options[] = {
    	{ "aggressive-nsec:", "yes" },
    	{ "fast-server-permil:", "950" },
    	{ "edns-buffer-size:", "1232" },
    	{ "target-fetch-policy:", "0 0 0 0 0" },
    	{ "outgoing-range:", "64" },
    	{ "val-max-restart:", "0" },
    	{ "infra-keep-probing", "yes" },
    };
    
    struct uw_resolver *
    create_resolver(enum uw_resolver_type type)
    {
    	struct uw_resolver	*res;
    	struct trust_anchor	*ta;
    	size_t			 i;
    	int			 err;
    	char			*resolv_conf;
    
    	if ((res = calloc(1, sizeof(*res))) == NULL) {
    		log_warn("%s", __func__);
    		return (NULL);
    	}
    
    	res->type = type;
    	res->state = UNKNOWN;
    	res->check_tv.tv_sec = RESOLVER_CHECK_SEC;
    	res->check_tv.tv_usec = arc4random() % 1000000; /* modulo bias is ok */
    
    	switch (type) {
    	case UW_RES_ASR:
    		if (TAILQ_EMPTY(&autoconf_forwarder_list)) {
    			free(res);
    			return (NULL);
    		}
    		if ((resolv_conf = gen_resolv_conf()) == NULL) {
    			free(res);
    			log_warnx("could not create asr context");
    			return (NULL);
    		}
    		if ((res->asr_ctx = asr_resolver_from_string(resolv_conf)) ==
    		    NULL) {
    			free(res);
    			free(resolv_conf);
    			log_warnx("could not create asr context");
    			return (NULL);
    		}
    		free(resolv_conf);
    		break;
    	case UW_RES_RECURSOR:
    	case UW_RES_AUTOCONF:
    	case UW_RES_ODOT_AUTOCONF:
    	case UW_RES_FORWARDER:
    	case UW_RES_ODOT_FORWARDER:
    	case UW_RES_DOT:
    		if ((res->ctx = ub_ctx_create_event(ev_base)) == NULL) {
    			free(res);
    			log_warnx("could not create unbound context");
    			return (NULL);
    		}
    
    		ub_ctx_debuglevel(res->ctx, log_getverbose() & OPT_VERBOSE3 ?
    		    UB_LOG_VERBOSE : UB_LOG_BRIEF);
    
    		TAILQ_FOREACH(ta, &trust_anchors, entry) {
    			if ((err = ub_ctx_add_ta(res->ctx, ta->ta)) != 0) {
    				ub_ctx_delete(res->ctx);
    				free(res);
    				log_warnx("error adding trust anchor: %s",
    				    ub_strerror(err));
    				return (NULL);
    			}
    		}
    
    		for (i = 0; i < nitems(options); i++) {
    			const char* option = options[i].value;
    
    			if (resolver_conf->force_resolvers[type] &&
    			    strcmp("aggressive-nsec:", options[i].name) == 0) {
    				/*
    				 * Do not enable aggressive-nsec caching,
    				 * because typos can lead to unresolvable
    				 * "force" domains if an nsec proof is cached.
    				 */
    				option = "no";
    			}
    			if ((err = ub_ctx_set_option(res->ctx, options[i].name,
    			    option)) != 0) {
    				ub_ctx_delete(res->ctx);
    				free(res);
    				log_warnx("error setting %s: %s: %s",
    				    options[i].name, options[i].value,
    				    ub_strerror(err));
    				return (NULL);
    			}
    		}
    
    		if (!(available_afs & HAVE_IPV4)) {
    			if((err = ub_ctx_set_option(res->ctx, "do-ip4:",
    			    "no")) != 0) {
    				ub_ctx_delete(res->ctx);
    				free(res);
    				log_warnx("error setting do-ip4: no: %s",
    				    ub_strerror(err));
    				return (NULL);
    			}
    		}
    
    		if (!(available_afs & HAVE_IPV6)) {
    			if((err = ub_ctx_set_option(res->ctx, "do-ip6:",
    			    "no")) != 0) {
    				ub_ctx_delete(res->ctx);
    				free(res);
    				log_warnx("error setting do-ip6: no: %s",
    				    ub_strerror(err));
    				return (NULL);
    			}
    		}
    
    		if (!log_getdebug()) {
    			if((err = ub_ctx_set_option(res->ctx, "use-syslog:",
    			    "no")) != 0) {
    				ub_ctx_delete(res->ctx);
    				free(res);
    				log_warnx("error setting use-syslog: no: %s",
    				    ub_strerror(err));
    				return (NULL);
    			}
    			ub_ctx_debugout(res->ctx, NULL);
    		}
    
    		break;
    	default:
    		fatalx("unknown resolver type %d", type);
    		break;
    	}
    
    	evtimer_set(&res->check_ev, resolver_check_timo, res);
    
    	switch(res->type) {
    	case UW_RES_ASR:
    		break;
    	case UW_RES_RECURSOR:
    		break;
    	case UW_RES_AUTOCONF:
    		set_forwarders(res, &autoconf_forwarder_list, 0);
    		break;
    	case UW_RES_ODOT_AUTOCONF:
    		set_forwarders(res, &autoconf_forwarder_list, 853);
    		ub_ctx_set_option(res->ctx, "tls-cert-bundle:",
    		    TLS_DEFAULT_CA_CERT_FILE);
    		ub_ctx_set_tls(res->ctx, 1);
    		break;
    	case UW_RES_FORWARDER:
    		set_forwarders(res, &resolver_conf->uw_forwarder_list, 0);
    		break;
    	case UW_RES_ODOT_FORWARDER:
    		set_forwarders(res, &resolver_conf->uw_forwarder_list, 853);
    		ub_ctx_set_option(res->ctx, "tls-cert-bundle:",
    		    TLS_DEFAULT_CA_CERT_FILE);
    		ub_ctx_set_tls(res->ctx, 1);
    		break;
    	case UW_RES_DOT:
    		set_forwarders(res, &resolver_conf->uw_dot_forwarder_list, 0);
    		ub_ctx_set_option(res->ctx, "tls-cert-bundle:",
    		    TLS_DEFAULT_CA_CERT_FILE);
    		ub_ctx_set_tls(res->ctx, 1);
    		break;
    	default:
    		fatalx("unknown resolver type %d", type);
    		break;
    	}
    
    	/* for the forwarder cases allow AS112 and special-use zones */
    	switch(res->type) {
    	case UW_RES_AUTOCONF:
    	case UW_RES_ODOT_AUTOCONF:
    	case UW_RES_FORWARDER:
    	case UW_RES_ODOT_FORWARDER:
    	case UW_RES_DOT:
    		for (i = 0; i < nitems(forward_transparent_zones); i++) {
    			if((err = ub_ctx_set_option(res->ctx, "local-zone:",
    			    forward_transparent_zones[i])) != 0) {
    				ub_ctx_delete(res->ctx);
    				free(res);
    				log_warnx("error setting local-zone: %s: %s",
    				    forward_transparent_zones[i],
    				    ub_strerror(err));
    				return (NULL);
    			}
    		}
    		break;
    	default:
    		break;
    	}
    
    	return (res);
    }
    
    void
    free_resolver(struct uw_resolver *res)
    {
    	if (res == NULL)
    		return;
    
    	if (res->ref_cnt > 0)
    		res->stop = 1;
    	else {
    		evtimer_del(&res->check_ev);
    #ifdef UNIFIED_CACHE
    		if (res->ctx != NULL) {
    			if (res->ctx->env->msg_cache == unified_msg_cache) {
    				struct val_env	*val_env;
    
    				val_env = (struct val_env*)
    				    res->ctx->env->modinfo[val_id];
    				res->ctx->env->msg_cache = NULL;
    				res->ctx->env->rrset_cache = NULL;
    				val_env->kcache = NULL;
    				res->ctx->env->key_cache = NULL;
    				val_env->neg_cache = NULL;
    				res->ctx->env->neg_cache = NULL;
    			}
    		}
    #endif /* UNIFIED_CACHE */
    		ub_ctx_delete(res->ctx);
    		asr_resolver_free(res->asr_ctx);
    		free(res);
    	}
    }
    
    #ifdef UNIFIED_CACHE
    void
    setup_unified_caches(void)
    {
    	struct ub_ctx	*ctx;
    	struct val_env	*val_env;
    	size_t		 i;
    	int		 err, j;
    
    	if ((ctx = ub_ctx_create_event(ev_base)) == NULL)
    		fatalx("could not create unbound context");
    
    	for (i = 0; i < nitems(options); i++) {
    		if ((err = ub_ctx_set_option(ctx, options[i].name,
    		    options[i].value)) != 0) {
    			fatalx("error setting %s: %s: %s", options[i].name,
    			    options[i].value, ub_strerror(err));
    		}
    	}
    
    	context_finalize(ctx);
    
    	if (ctx->env->msg_cache == NULL || ctx->env->rrset_cache == NULL ||
    	    ctx->env->key_cache == NULL || ctx->env->neg_cache == NULL)
    		fatalx("could not setup unified caches");
    
    	unified_msg_cache = ctx->env->msg_cache;
    	unified_rrset_cache = ctx->env->rrset_cache;
    	unified_key_cache = ctx->env->key_cache;
    	unified_neg_cache = ctx->env->neg_cache;
    
    	if (val_id == -1) {
    		for (j = 0; j < ctx->mods.num; j++) {
    			if (strcmp(ctx->mods.mod[j]->name, "validator") == 0) {
    				val_id = j;
    				break;
    			}
    		}
    		if (val_id == -1)
    			fatalx("cannot find validator module");
    	}
    
    	val_env = (struct val_env*)ctx->env->modinfo[val_id];
    	ctx->env->msg_cache = NULL;
    	ctx->env->rrset_cache = NULL;
    	ctx->env->key_cache = NULL;
    	val_env->kcache = NULL;
    	ctx->env->neg_cache = NULL;
    	val_env->neg_cache = NULL;
    	ub_ctx_delete(ctx);
    }
    #endif /* UNIFIED_CACHE */
    
    void
    set_forwarders(struct uw_resolver *res, struct uw_forwarder_head
        *uw_forwarder_list, int port_override)
    {
    	struct uw_forwarder	*uw_forwarder;
    	int			 ret;
    	char			 fwd[FWD_MAX];
    
    	TAILQ_FOREACH(uw_forwarder, uw_forwarder_list, entry) {
    		if (uw_forwarder->auth_name[0] != '\0')
    			ret = snprintf(fwd, sizeof(fwd), "%s@%d#%s",
    			    uw_forwarder->ip, port_override ? port_override :
    			    uw_forwarder->port, uw_forwarder->auth_name);
    		else
    			ret = snprintf(fwd, sizeof(fwd), "%s@%d",
    			    uw_forwarder->ip, port_override ? port_override :
    			    uw_forwarder->port);
    
    		if (ret < 0 || (size_t)ret >= sizeof(fwd)) {
    			log_warnx("forwarder too long");
    			continue;
    		}
    
    		ub_ctx_set_fwd(res->ctx, fwd);
    	}
    }
    
    void
    resolver_check_timo(int fd, short events, void *arg)
    {
    	check_resolver((struct uw_resolver *)arg);
    }
    
    void
    resolver_free_timo(int fd, short events, void *arg)
    {
    	free_resolver((struct uw_resolver *)arg);
    }
    
    void
    check_resolver(struct uw_resolver *resolver_to_check)
    {
    	struct uw_resolver		*res;
    
    	if (resolver_to_check == NULL)
    		return;
    
    	if (resolver_to_check->check_running)
    		return;
    
    	if ((res = create_resolver(resolver_to_check->type)) == NULL)
    		return;
    
    	resolver_ref(resolver_to_check);
    
    	resolver_to_check->check_running++;
    	if (resolve(res, ".", LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN,
    	    resolver_to_check, check_resolver_done) != 0) {
    		resolver_to_check->check_running--;
    		resolver_to_check->state = UNKNOWN;
    		resolver_unref(resolver_to_check);
    		resolver_to_check->check_tv.tv_sec = RESOLVER_CHECK_SEC;
    		evtimer_add(&resolver_to_check->check_ev,
    		    &resolver_to_check->check_tv);
    	}
    }
    
    void
    check_resolver_done(struct uw_resolver *res, void *arg, int rcode,
        void *answer_packet, int answer_len, int sec, char *why_bogus)
    {
    	struct uw_resolver	*checked_resolver = arg;
    	struct timeval		 tv = {0, 1};
    	enum uw_resolver_state	 prev_state;
    	int			 bogus_time = 0;
    	char			*str;
    
    	checked_resolver->check_running--;
    
    	if (checked_resolver != resolvers[checked_resolver->type]) {
    		log_debug("%s: %s: ignoring late check result", __func__,
    		    uw_resolver_type_str[checked_resolver->type]);
    		goto ignore_late;
    	}
    
    	prev_state = checked_resolver->state;
    
    	if (rcode == LDNS_RCODE_SERVFAIL) {
    		log_debug("%s: %s rcode: SERVFAIL", __func__,
    		    uw_resolver_type_str[checked_resolver->type]);
    
    		checked_resolver->state = DEAD;
    		goto out;
    	}
    
    	if (answer_len < LDNS_HEADER_SIZE) {
    		checked_resolver->state = DEAD;
    		log_warnx("%s: bad packet: too short", __func__);
    		goto out;
    	}
    
    	if (sec == SECURE) {
    		if (dns64_present && (res->type == UW_RES_AUTOCONF ||
    		    res->type == UW_RES_ODOT_AUTOCONF)) {
    			/* do not upgrade to validating, DNS64 breaks DNSSEC */
    			if (prev_state != RESOLVING)
    				new_resolver(checked_resolver->type,
    				    RESOLVING);
    		} else {
    			if (prev_state != VALIDATING)
    				new_resolver(checked_resolver->type,
    				    VALIDATING);
    			if (!(evtimer_pending(&trust_anchor_timer, NULL)))
    				evtimer_add(&trust_anchor_timer, &tv);
    		}
    	 } else if (rcode == LDNS_RCODE_NOERROR &&
    	    LDNS_RCODE_WIRE((uint8_t*)answer_packet) == LDNS_RCODE_NOERROR) {
    		if (why_bogus) {
    			bogus_time = strncmp(why_bogus, bogus_past,
    			    sizeof(bogus_past) - 1) == 0 || strncmp(why_bogus,
    			    bogus_future, sizeof(bogus_future) - 1) == 0;
    
    			log_warnx("%s: %s", uw_resolver_type_str[
    			    checked_resolver->type], why_bogus);
    		}
    		if (prev_state != RESOLVING)
    			new_resolver(checked_resolver->type, RESOLVING);
    	} else
    		checked_resolver->state = DEAD; /* we know the root exists */
    
    	log_debug("%s: %s: %s", __func__,
    	    uw_resolver_type_str[checked_resolver->type],
    	    uw_resolver_state_str[checked_resolver->state]);
    
    	if (log_getverbose() & OPT_VERBOSE2 && (str =
    	    sldns_wire2str_pkt(answer_packet, answer_len)) != NULL) {
    		log_debug("%s", str);
    		free(str);
    	}
    
    out:
    	if (!checked_resolver->stop && (checked_resolver->state == DEAD ||
    	    bogus_time)) {
    		if (prev_state == DEAD || bogus_time)
    			checked_resolver->check_tv.tv_sec *= 2;
    		else
    			checked_resolver->check_tv.tv_sec = RESOLVER_CHECK_SEC;
    
    		if (checked_resolver->check_tv.tv_sec > RESOLVER_CHECK_MAXSEC)
    			checked_resolver->check_tv.tv_sec =
    			    RESOLVER_CHECK_MAXSEC;
    
    		evtimer_add(&checked_resolver->check_ev,
    		    &checked_resolver->check_tv);
    	}
    
    ignore_late:
    	resolver_unref(checked_resolver);
    	res->stop = 1; /* do not free in callback */
    }
    
    void
    asr_resolve_done(struct asr_result *ar, void *arg)
    {
    	struct resolver_cb_data	*cb_data = arg;
    	cb_data->cb(cb_data->res, cb_data->data, ar->ar_errno == 0 ?
    	    ar->ar_rcode : LDNS_RCODE_SERVFAIL, ar->ar_data, ar->ar_datalen, 0,
    	    NULL);
    	free(ar->ar_data);
    	resolver_unref(cb_data->res);
    	free(cb_data);
    }
    
    void
    ub_resolve_done(void *arg, int rcode, void *answer_packet, int answer_len,
        int sec, char *why_bogus, int was_ratelimited)
    {
    	struct resolver_cb_data	*cb_data = arg;
    	cb_data->cb(cb_data->res, cb_data->data, rcode, answer_packet,
    	    answer_len, sec, why_bogus);
    	resolver_unref(cb_data->res);
    	free(cb_data);
    }
    
    void
    schedule_recheck_all_resolvers(void)
    {
    	struct timeval	 tv;
    	int		 i;
    
    	tv.tv_sec = 0;
    
    	for (i = 0; i < UW_RES_NONE; i++) {
    		if (resolvers[i] == NULL)
    			continue;
    		tv.tv_usec = arc4random() % 1000000; /* modulo bias is ok */
    		resolvers[i]->state = UNKNOWN;
    		evtimer_add(&resolvers[i]->check_ev, &tv);
    	}
    }
    
    int
    check_forwarders_changed(struct uw_forwarder_head *list_a,
        struct uw_forwarder_head *list_b)
    {
    	struct uw_forwarder	*a, *b;
    
    	a = TAILQ_FIRST(list_a);
    	b = TAILQ_FIRST(list_b);
    
    	while(a != NULL && b != NULL) {
    		if (strcmp(a->ip, b->ip) != 0)
    			return 1;
    		if (a->port != b->port)
    			return 1;
    		if (strcmp(a->auth_name, b->auth_name) != 0)
    			return 1;
    		a = TAILQ_NEXT(a, entry);
    		b = TAILQ_NEXT(b, entry);
    	}
    
    	if (a != NULL || b != NULL)
    		return 1;
    	return 0;
    }
    
    void
    resolver_ref(struct uw_resolver *res)
    {
    	if (res->ref_cnt == INT_MAX)
    		fatalx("%s: INT_MAX references", __func__);
    	res->ref_cnt++;
    }
    
    void
    resolver_unref(struct uw_resolver *res)
    {
    	struct timeval	 tv = { 0, 1};
    
    	if (res->ref_cnt == 0)
    		fatalx("%s: unreferenced resolver", __func__);
    
    	res->ref_cnt--;
    
    	/*
    	 * Decouple from libunbound event callback.
    	 * If we free the ctx inside of resolve_done or check_resovler_done
    	 * we are cutting of the branch we are sitting on and hit a
    	 * user-after-free
    	 */
    	if (res->stop && res->ref_cnt == 0) {
    		evtimer_set(&res->free_ev, resolver_free_timo, res);
    		evtimer_add(&res->free_ev, &tv);
    	}
    }
    
    void
    replace_forwarders(struct uw_forwarder_head *new_list, struct
        uw_forwarder_head *old_list)
    {
    	struct uw_forwarder	*uw_forwarder;
    
    	while ((uw_forwarder =
    	    TAILQ_FIRST(old_list)) != NULL) {
    		TAILQ_REMOVE(old_list, uw_forwarder, entry);
    		free(uw_forwarder);
    	}
    
    	TAILQ_CONCAT(old_list, new_list, entry);
    }
    
    int
    resolver_cmp(const void *_a, const void *_b)
    {
    	const enum uw_resolver_type	 a = *(const enum uw_resolver_type *)_a;
    	const enum uw_resolver_type	 b = *(const enum uw_resolver_type *)_b;
    	int64_t				 a_median, b_median;
    
    	if (resolvers[a] == NULL && resolvers[b] == NULL)
    		return 0;
    
    	if (resolvers[b] == NULL)
    		return -1;
    
    	if (resolvers[a] == NULL)
    		return 1;
    
    	if (resolvers[a]->state < resolvers[b]->state)
    		return 1;
    	else if (resolvers[a]->state > resolvers[b]->state)
    		return -1;
    	else {
    		a_median = resolvers[a]->median;
    		b_median = resolvers[b]->median;
    		if (resolvers[a]->type == resolver_conf->res_pref.types[0])
    			a_median -= PREF_RESOLVER_MEDIAN_SKEW;
    		else if (resolvers[b]->type == resolver_conf->res_pref.types[0])
    			b_median -= PREF_RESOLVER_MEDIAN_SKEW;
    		if (a_median < b_median)
    			return -1;
    		else if (a_median > b_median)
    			return 1;
    		else
    			return 0;
    	}
    }
    
    void
    restart_ub_resolvers(int recheck)
    {
    	int			 i;
    	enum uw_resolver_state	 state;
    
    	for (i = 0; i < UW_RES_NONE; i++) {
    		if (i == UW_RES_ASR)
    			continue;
    		if (recheck || resolvers[i] == NULL)
    			state = UNKNOWN;
    		else
    			state = resolvers[i]->state;
    		new_resolver(i, state);
    	}
    }
    
    void
    show_status(pid_t pid)
    {
    	struct resolver_preference	 res_pref;
    	int				 i;
    
    	if (sort_resolver_types(&res_pref) == -1)
    		log_warn("mergesort");
    
    	for (i = 0; i < resolver_conf->res_pref.len; i++)
    		send_resolver_info(resolvers[res_pref.types[i]], pid);
    
    	resolver_imsg_compose_frontend(IMSG_CTL_END, pid, NULL, 0);
    }
    
    void
    show_autoconf(pid_t pid)
    {
    	struct uw_forwarder		*uw_forwarder;
    	struct ctl_forwarder_info	 cfi;
    
    	TAILQ_FOREACH(uw_forwarder, &autoconf_forwarder_list, entry) {
    		memset(&cfi, 0, sizeof(cfi));
    		cfi.if_index = uw_forwarder->if_index;
    		cfi.src = uw_forwarder->src;
    		/* no truncation, structs are in sync */
    		memcpy(cfi.ip, uw_forwarder->ip, sizeof(cfi.ip));
    		resolver_imsg_compose_frontend(
    		    IMSG_CTL_AUTOCONF_RESOLVER_INFO,
    		    pid, &cfi, sizeof(cfi));
    	}
    
    	resolver_imsg_compose_frontend(IMSG_CTL_END, pid, NULL, 0);
    }
    
    void
    show_mem(pid_t pid)
    {
    	struct ctl_mem_info	 cmi;
    
    	memset(&cmi, 0, sizeof(cmi));
    #ifdef UNIFIED_CACHE
    	cmi.msg_cache_used = slabhash_get_mem(unified_msg_cache);
    	cmi.msg_cache_max = slabhash_get_size(unified_msg_cache);
    	cmi.rrset_cache_used = slabhash_get_mem(&unified_rrset_cache->table);
    	cmi.rrset_cache_max = slabhash_get_size(&unified_rrset_cache->table);
    	cmi.key_cache_used = slabhash_get_mem(unified_key_cache->slab);
    	cmi.key_cache_max = slabhash_get_size(unified_key_cache->slab);
    	cmi.neg_cache_used = unified_neg_cache->use;
    	cmi.neg_cache_max = unified_neg_cache->max;
    #endif /* UNIFIED_CACHE */
    	resolver_imsg_compose_frontend(IMSG_CTL_MEM_INFO, pid, &cmi,
    	    sizeof(cmi));
    
    }
    
    void
    send_resolver_info(struct uw_resolver *res, pid_t pid)
    {
    	struct ctl_resolver_info	 cri;
    	size_t				 i;
    
    	if (res == NULL)
    		return;
    
    	cri.state = res->state;
    	cri.type = res->type;
    	cri.median = res->median;
    
    	memcpy(cri.histogram, res->histogram, sizeof(cri.histogram));
    	memcpy(cri.latest_histogram, res->latest_histogram,
    	    sizeof(cri.latest_histogram));
    	for (i = 0; i < nitems(histogram_limits); i++)
    		cri.latest_histogram[i] =
    		    (cri.latest_histogram[i] + 500) / 1000;
    
    	resolver_imsg_compose_frontend(IMSG_CTL_RESOLVER_INFO, pid, &cri,
    	    sizeof(cri));
    }
    
    void
    trust_anchor_resolve(void)
    {
    	struct resolver_preference	 res_pref;
    	struct uw_resolver		*res;
    	struct timeval			 tv = {TRUST_ANCHOR_RETRY_INTERVAL, 0};
    
    	if (sort_resolver_types(&res_pref) == -1)
    		log_warn("mergesort");
    
    	res = resolvers[res_pref.types[0]];
    
    	if (res == NULL || res->state < VALIDATING)
    		goto err;
    
    	if (resolve(res, ".",  LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN, NULL,
    	    trust_anchor_resolve_done) != 0)
    		goto err;
    
    	return;
     err:
    	evtimer_add(&trust_anchor_timer, &tv);
    }
    
    void
    trust_anchor_timo(int fd, short events, void *arg)
    {
    	trust_anchor_resolve();
    }
    
    void
    trust_anchor_resolve_done(struct uw_resolver *res, void *arg, int rcode,
        void *answer_packet, int answer_len, int sec, char *why_bogus)
    {
    	struct ub_result	*result = NULL;
    	sldns_buffer		*buf = NULL;
    	struct regional		*region = NULL;
    	struct timeval		 tv = {TRUST_ANCHOR_RETRY_INTERVAL, 0};
    	int			 i, tas, n;
    	uint16_t		 dnskey_flags;
    	char			 rdata_buf[1024], *ta;
    
    	if (rcode == LDNS_RCODE_SERVFAIL) {
    		log_debug("%s: rcode: SERVFAIL", __func__);
    		goto out;
    	}
    
    	if (answer_len < LDNS_HEADER_SIZE) {
    		log_warnx("bad packet: too short");
    		goto out;
    	}
    
    	if ((result = calloc(1, sizeof(*result))) == NULL)
    		goto out;
    
    	if (sec != SECURE)
    		goto out;
    
    	if ((buf = sldns_buffer_new(answer_len)) == NULL)
    		goto out;
    	if ((region = regional_create()) == NULL)
    		goto out;
    	result->rcode = LDNS_RCODE_SERVFAIL;
    
    	sldns_buffer_clear(buf);
    	sldns_buffer_write(buf, answer_packet, answer_len);
    	sldns_buffer_flip(buf);
    	libworker_enter_result(result, buf, region, sec);
    	result->answer_packet = NULL;
    	result->answer_len = 0;
    
    	if (result->rcode != LDNS_RCODE_NOERROR)
    		goto out;
    
    	i = 0;
    	tas = 0;
    	while(result->data[i] != NULL) {
    		if (result->len[i] < 2) {
    			if (tas > 0)
    				resolver_imsg_compose_frontend(
    				    IMSG_NEW_TAS_ABORT, 0, NULL, 0);
    			goto out;
    		}
    		n = sldns_wire2str_rdata_buf(result->data[i], result->len[i],
    		    rdata_buf, sizeof(rdata_buf), LDNS_RR_TYPE_DNSKEY);
    
    		if (n < 0 || (size_t)n >= sizeof(rdata_buf)) {
    			log_warnx("trust anchor buffer to small");
    			resolver_imsg_compose_frontend(IMSG_NEW_TAS_ABORT, 0,
    			    NULL, 0);
    			goto out;
    		}
    
    		memcpy(&dnskey_flags, result->data[i], 2);
    		dnskey_flags = ntohs(dnskey_flags);
    		if ((dnskey_flags & LDNS_KEY_SEP_KEY) && !(dnskey_flags &
    		    LDNS_KEY_REVOKE_KEY)) {
    			asprintf(&ta, ".\t%d\tIN\tDNSKEY\t%s", ROOT_DNSKEY_TTL,
    			    rdata_buf);
    			resolver_imsg_compose_frontend(IMSG_NEW_TA, 0, ta,
    			    strlen(ta) + 1);
    			tas++;
    			free(ta);
    		}
    		i++;
    	}
    	if (tas > 0) {
    		resolver_imsg_compose_frontend(IMSG_NEW_TAS_DONE, 0, NULL, 0);
    		tv.tv_sec = TRUST_ANCHOR_QUERY_INTERVAL;
    	}
    out:
    	sldns_buffer_free(buf);
    	regional_destroy(region);
    	ub_resolve_free(result);
    	evtimer_add(&trust_anchor_timer, &tv);
    }
    
    void
    replace_autoconf_forwarders(struct imsg_rdns_proposal *rdns_proposal)
    {
    	struct uw_forwarder_head	 new_forwarder_list;
    	struct uw_forwarder		*uw_forwarder, *tmp;
    	size_t				 addrsz;
    	int				 i, rdns_count, af, changed = 0;
    	char				 hostbuf[INET6_ADDRSTRLEN], *src;
    
    	TAILQ_INIT(&new_forwarder_list);
    	af = rdns_proposal->rtdns.sr_family;
    	src = rdns_proposal->rtdns.sr_dns;
    
    	switch (af) {
    	case AF_INET:
    		addrsz = sizeof(struct in_addr);
    		break;
    	case AF_INET6:
    		addrsz = sizeof(struct in6_addr);
    		break;
    	default:
    		log_warnx("%s: unsupported address family: %d", __func__, af);
    		return;
    	}
    
    	if ((rdns_proposal->rtdns.sr_len - 2) % addrsz != 0) {
    		log_warnx("ignoring invalid RTM_PROPOSAL");
    		return;
    	}
    	rdns_count = (rdns_proposal->rtdns.sr_len -
    	    offsetof(struct sockaddr_rtdns, sr_dns)) / addrsz;
    
    	for (i = 0; i < rdns_count; i++) {
    		struct sockaddr_storage ss;
    		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
    		struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
    		int err;
    
    		memset(&ss, 0, sizeof(ss));
    		ss.ss_family = af;
    		switch (af) {
    		case AF_INET:
    			memcpy(&sin->sin_addr, src, addrsz);
    			if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
    				goto skip;
    			ss.ss_len = sizeof(*sin);
    			break;
    		case AF_INET6:
    			memcpy(&sin6->sin6_addr, src, addrsz);
    			if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))
    				goto skip;
    			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
    				sin6->sin6_scope_id = rdns_proposal->if_index;
    			ss.ss_len = sizeof(*sin6);
    			break;
    		}
    		if ((err = getnameinfo((struct sockaddr *)&ss, ss.ss_len,
    		    hostbuf, sizeof(hostbuf), NULL, 0, NI_NUMERICHOST)) != 0) {
    			log_warnx("getnameinfo: %s", gai_strerror(err));
    			goto skip;
    		}
    
    		if ((uw_forwarder = calloc(1, sizeof(struct uw_forwarder))) ==
    		    NULL)
    			fatal(NULL);
    		if (strlcpy(uw_forwarder->ip, hostbuf, sizeof(uw_forwarder->ip))
    		    >= sizeof(uw_forwarder->ip))
    			fatalx("strlcpy");
    		uw_forwarder->port = 53;
    		uw_forwarder->if_index = rdns_proposal->if_index;
    		uw_forwarder->src = rdns_proposal->src;
    		TAILQ_INSERT_TAIL(&new_forwarder_list, uw_forwarder, entry);
    
    skip:
    		src += addrsz;
    	}
    
    	TAILQ_FOREACH(tmp, &autoconf_forwarder_list, entry) {
    		/*
    		 * if_index of zero signals to clear all proposals
    		 * src of zero signals interface gone
    		 */
    		if ((rdns_proposal->src == 0 || rdns_proposal->src ==
    		    tmp->src) && (rdns_proposal->if_index == 0 ||
    		    rdns_proposal->if_index == tmp->if_index))
    			continue;
    		if ((uw_forwarder = calloc(1, sizeof(struct uw_forwarder))) ==
    		    NULL)
    			fatal(NULL);
    		if (strlcpy(uw_forwarder->ip, tmp->ip,
    		    sizeof(uw_forwarder->ip)) >= sizeof(uw_forwarder->ip))
    			fatalx("strlcpy");
    		uw_forwarder->port = tmp->port;
    		uw_forwarder->src = tmp->src;
    		uw_forwarder->if_index = tmp->if_index;
    		TAILQ_INSERT_TAIL(&new_forwarder_list, uw_forwarder, entry);
    	}
    
    	changed = check_forwarders_changed(&new_forwarder_list,
    	    &autoconf_forwarder_list);
    
    	if (changed) {
    		replace_forwarders(&new_forwarder_list,
    		    &autoconf_forwarder_list);
    		new_resolver(UW_RES_ASR, UNKNOWN);
    		new_resolver(UW_RES_AUTOCONF, UNKNOWN);
    		new_resolver(UW_RES_ODOT_AUTOCONF, UNKNOWN);
    	} else {
    		while ((tmp = TAILQ_FIRST(&new_forwarder_list)) != NULL) {
    			TAILQ_REMOVE(&new_forwarder_list, tmp, entry);
    			free(tmp);
    		}
    	}
    }
    
    int
    force_tree_cmp(struct force_tree_entry *a, struct force_tree_entry *b)
    {
    	return strcasecmp(a->domain, b->domain);
    }
    
    int
    find_force(struct force_tree *tree, char *qname, struct uw_resolver **res)
    {
    	struct force_tree_entry	*n, e;
    	char 			*p;
    
    	if (res)
    		*res = NULL;
    	if (RB_EMPTY(tree))
    		return 0;
    
    	p = qname;
    	do {
    		if (strlcpy(e.domain, p, sizeof(e.domain)) >= sizeof(e.domain))
    			fatal("qname too large");
    		n = RB_FIND(force_tree, tree, &e);
    		if (n != NULL) {
    			log_debug("%s: %s -> %s[%s]", __func__, qname, p,
    			    uw_resolver_type_str[n->type]);
    			if (res)
    				*res = resolvers[n->type];
    			return n->acceptbogus;
    		}
    		if (*p == '.')
    			p++;
    		p = strchr(p, '.');
    		if (p != NULL && p[1] != '\0')
    			p++;
    	} while (p != NULL);
    	return 0;
    
    }
    
    int64_t
    histogram_median(int64_t *histogram)
    {
    	size_t	 i;
    	int64_t	 sample_count = 0, running_count = 0;
    
    	/* skip first bucket, it contains cache hits */
    	for (i = 1; i < nitems(histogram_limits); i++)
    		sample_count += histogram[i];
    
    	if (sample_count == 0)
    		return 0;
    
    	for (i = 1; i < nitems(histogram_limits); i++) {
    		running_count += histogram[i];
    		if (running_count >= sample_count / 2)
    			break;
    	}
    
    	if (i >= nitems(histogram_limits) - 1)
    		return INT64_MAX;
    	return (histogram_limits[i - 1] + histogram_limits[i]) / 2;
    }
    
    void
    decay_latest_histograms(int fd, short events, void *arg)
    {
    	enum uw_resolver_type	 i;
    	size_t			 j;
    	struct uw_resolver	*res;
    	struct timeval		 tv = {DECAY_PERIOD, 0};
    
    	for (i = 0; i < UW_RES_NONE; i++) {
    		res = resolvers[i];
    		if (res == NULL)
    			continue;
    		for (j = 0; j < nitems(res->latest_histogram); j++)
    			/* multiply then divide, avoiding truncating to 0 */
    			res->latest_histogram[j] = res->latest_histogram[j] *
    			    DECAY_NOMINATOR / DECAY_DENOMINATOR;
    		res->median = histogram_median(res->latest_histogram);
    	}
    	evtimer_add(&decay_timer, &tv);
    }
    
    int
    running_query_cnt(void)
    {
    	struct running_query	*e;
    	int			 cnt = 0;
    
    	TAILQ_FOREACH(e, &running_queries, entry)
    		cnt++;
    	return cnt;
    }
    
    int *
    resolvers_to_restart(struct uw_conf *oconf, struct uw_conf *nconf)
    {
    	static int	 restart[UW_RES_NONE];
    	int		 i;
    
    	memset(&restart, 0, sizeof(restart));
    	if (check_forwarders_changed(&oconf->uw_forwarder_list,
    	    &nconf->uw_forwarder_list)) {
    		restart[UW_RES_FORWARDER] = 1;
    		restart[UW_RES_ODOT_FORWARDER] = 1;
    	}
    	if (check_forwarders_changed(&oconf->uw_dot_forwarder_list,
    	    &nconf->uw_dot_forwarder_list)) {
    		restart[UW_RES_DOT] = 1;
    	}
    
    	for (i = 0; i < UW_RES_NONE; i++) {
    		if (oconf->enabled_resolvers[i] != nconf->enabled_resolvers[i])
    			restart[i] = 1;
    	}
    	return restart;
    }
    
    const char *
    query_imsg2str(struct query_imsg *query_imsg)
    {
    	static char	 buf[sizeof(query_imsg->qname) + 1 + 16 + 1 + 16];
    	char		 qclass_buf[16];
    	char		 qtype_buf[16];
    
    	sldns_wire2str_class_buf(query_imsg->c, qclass_buf, sizeof(qclass_buf));
    	sldns_wire2str_type_buf(query_imsg->t, qtype_buf, sizeof(qtype_buf));
    
    	snprintf(buf, sizeof(buf), "%s %s %s", query_imsg->qname, qclass_buf,
    	    qtype_buf);
    	return buf;
    }
    
    char *
    gen_resolv_conf(void)
    {
    	struct uw_forwarder	*uw_forwarder;
    	char			*resolv_conf = NULL, *tmp = NULL;
    
    	TAILQ_FOREACH(uw_forwarder, &autoconf_forwarder_list, entry) {
    		tmp = resolv_conf;
    		if (asprintf(&resolv_conf, "%snameserver %s\n", tmp ==
    		    NULL ? "" : tmp, uw_forwarder->ip) == -1) {
    			free(tmp);
    			return (NULL);
    		}
    		free(tmp);
    	}
    	return resolv_conf;
    }
    
    void
    check_dns64(void)
    {
    	struct asr_query	*aq = NULL;
    	char			*resolv_conf;
    	void			*asr_ctx;
    
    	if (TAILQ_EMPTY(&autoconf_forwarder_list))
    		return;
    
    	if ((resolv_conf = gen_resolv_conf()) == NULL) {
    		log_warnx("could not create asr context");
    		return;
    	}
    
    	if ((asr_ctx = asr_resolver_from_string(resolv_conf)) != NULL) {
    		if ((aq = res_query_async("ipv4only.arpa.", LDNS_RR_CLASS_IN,
    		    LDNS_RR_TYPE_AAAA, asr_ctx)) == NULL) {
    			log_warn("%s: res_query_async", __func__);
    			asr_resolver_free(asr_ctx);
    		}
    		if (event_asr_run(aq, check_dns64_done, asr_ctx) == NULL) {
    			log_warn("%s: event_asr_run", __func__);
    			free(aq);
    			asr_resolver_free(asr_ctx);
    		}
    	} else
    		log_warnx("%s: could not create asr context", __func__);
    
    	free(resolv_conf);
    }
    
    void
    check_dns64_done(struct asr_result *ar, void *arg)
    {
    	/* RFC 7050: ipv4only.arpa resolves to 192.0.0.170 and 192.9.0.171 */
    	const uint8_t			 wka1[] = {192, 0, 0, 170};
    	const uint8_t			 wka2[] = {192, 0, 0, 171};
    	struct query_info		 skip, qinfo;
    	struct reply_info		*rinfo = NULL;
    	struct regional			*region = NULL;
    	struct sldns_buffer		*buf = NULL;
    	struct ub_packed_rrset_key	*an_rrset = NULL;
    	struct packed_rrset_data	*an_rrset_data;
    	struct alloc_cache		 alloc;
    	struct edns_data		 edns;
    	struct dns64_prefix		*prefixes = NULL;
    	size_t				 i;
    	int				 preflen, count = 0;
    	void				*asr_ctx = arg;
    
    	if (ar->ar_errno != 0)
    		goto fail;
    
    	memset(&qinfo, 0, sizeof(qinfo));
    	alloc_init(&alloc, NULL, 0);
    
    	if (ar->ar_datalen < LDNS_HEADER_SIZE) {
    		log_warnx("%s: bad packet: too short: %d", __func__,
    		    ar->ar_datalen);
    		goto out;
    	}
    
    	if (ar->ar_datalen > UINT16_MAX) {
    		log_warnx("%s: bad packet: too large: %d", __func__,
    		    ar->ar_datalen);
    		goto out;
    	}
    
    	if (ar->ar_rcode == LDNS_RCODE_NXDOMAIN) {
    		/* XXX this means that the autoconf resolver is broken */
    		log_debug("%s: NXDOMAIN", __func__);
    		goto out;
    	}
    
    	if ((buf = sldns_buffer_new(ar->ar_datalen)) == NULL)
    		goto out;
    
    	if ((region = regional_create()) == NULL)
    		goto out;
    
    	sldns_buffer_write(buf, ar->ar_data, ar->ar_datalen);
    	sldns_buffer_flip(buf);
    
    	/* read past query section, no memory is allocated */
    	if (!query_info_parse(&skip, buf))
    		goto out;
    
    	if (reply_info_parse(buf, &alloc, &qinfo, &rinfo, region, &edns) != 0)
    		goto out;
    
    	if ((an_rrset = reply_find_answer_rrset(&qinfo, rinfo)) == NULL)
    		goto out;
    
    	an_rrset_data = (struct packed_rrset_data*)an_rrset->entry.data;
    
    	prefixes = calloc(an_rrset_data->count, sizeof(struct dns64_prefix));
    	if (prefixes == NULL)
    		goto out;
    
    	for (i = 0; i < an_rrset_data->count; i++) {
    		struct in6_addr	 in6;
    
    		/* check for AAAA record */
    		if (an_rrset_data->rr_len[i] != 18) /* 2 + 128/8 */
    			continue;
    		if (an_rrset_data->rr_data[i][0] != 0 &&
    		    an_rrset_data->rr_data[i][1] != 16)
    			continue;
    
    		memcpy(&in6, &an_rrset_data->rr_data[i][2],
    		    sizeof(in6));
    		if ((preflen = dns64_prefixlen(&in6, wka1)) != -1)
    			add_dns64_prefix(&in6, preflen, prefixes,
    			    an_rrset_data->count, WKA1_FOUND);
    		if ((preflen = dns64_prefixlen(&in6, wka2)) != -1)
    			add_dns64_prefix(&in6, preflen, prefixes,
    			    an_rrset_data->count, WKA2_FOUND);
    	}
    
    	for (i = 0; i < an_rrset_data->count && prefixes[i].flags != 0; i++)
    		if ((prefixes[i].flags & (WKA1_FOUND | WKA2_FOUND)) ==
    		    (WKA1_FOUND | WKA2_FOUND))
    			count++;
    
    	dns64_present = count > 0;
    
    	if (dns64_present) {
    		/* downgrade SLAAC resolvers, DNS64 breaks DNSSEC */
    		if (resolvers[UW_RES_AUTOCONF] != NULL &&
    		    resolvers[UW_RES_AUTOCONF]->state == VALIDATING)
    			new_resolver(UW_RES_AUTOCONF, RESOLVING);
    		if (resolvers[UW_RES_ODOT_AUTOCONF] != NULL &&
    		    resolvers[UW_RES_ODOT_AUTOCONF]->state == VALIDATING)
    			new_resolver(UW_RES_ODOT_AUTOCONF, RESOLVING);
    	}
    
    	resolver_imsg_compose_frontend(IMSG_NEW_DNS64_PREFIXES_START, 0,
    	    &count, sizeof(count));
    	for (i = 0; i < an_rrset_data->count && prefixes[i].flags != 0; i++) {
    		if ((prefixes[i].flags & (WKA1_FOUND | WKA2_FOUND)) ==
    		    (WKA1_FOUND | WKA2_FOUND)) {
    			resolver_imsg_compose_frontend(IMSG_NEW_DNS64_PREFIX,
    			    0, &prefixes[i], sizeof(struct dns64_prefix));
    		}
    	}
    	resolver_imsg_compose_frontend(IMSG_NEW_DNS64_PREFIXES_DONE, 0, NULL,
    	    0);
     out:
    	free(prefixes);
    	query_info_clear(&qinfo);
    	reply_info_parsedelete(rinfo, &alloc);
    	alloc_clear(&alloc);
    	regional_destroy(region);
    	sldns_buffer_free(buf);
     fail:
    	free(ar->ar_data);
    	asr_resolver_free(asr_ctx);
    }
    
    int
    dns64_prefixlen(const struct in6_addr *in6, const uint8_t *wka)
    {
    	/* RFC 6052, 2.2 */
    	static const int	 possible_prefixes[] = {32, 40, 48, 56, 64, 96};
    	size_t			 i, j;
    	int			 found, pos;
    
    	for (i = 0; i < nitems(possible_prefixes); i++) {
    		pos = possible_prefixes[i] / 8;
    		found = 1;
    		for (j = 0; j < 4 && found; j++, pos++) {
    			if (pos == 8) {
    				if (in6->s6_addr[pos] != 0)
    					found = 0;
    				pos++;
    			}
    			if (in6->s6_addr[pos] != wka[j])
    				found = 0;
    		}
    		if (found)
    			return possible_prefixes[i];
    	}
    	return -1;
    }
    
    void
    add_dns64_prefix(const struct in6_addr *in6, int prefixlen,
        struct dns64_prefix *prefixes, int prefixes_size, int flag)
    {
    	struct in6_addr	 tmp;
    	int		 i;
    
    	tmp = *in6;
    
    	for(i = prefixlen / 8; i < 16; i++)
    		tmp.s6_addr[i] = 0;
    
    	for (i = 0; i < prefixes_size; i++) {
    		if (prefixes[i].flags == 0) {
    			prefixes[i].in6 = tmp;
    			prefixes[i].prefixlen = prefixlen;
    			prefixes[i].flags |= flag;
    			break;
    		} else if (prefixes[i].prefixlen == prefixlen &&
    		    memcmp(&prefixes[i].in6, &tmp, sizeof(tmp)) == 0) {
    			prefixes[i].flags |= flag;
    			break;
    		}
    	}
    }