root/sbin/unwind/frontend.c
/*      $OpenBSD: frontend.c,v 1.92 2025/04/27 16:21:26 florian Exp $   */

/*
 * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
 * Copyright (c) 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/tree.h>
#include <sys/uio.h>

#include <netinet/in.h>
#include <net/if.h>
#include <net/route.h>

#include <errno.h>
#include <event.h>
#include <ifaddrs.h>
#include <imsg.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "libunbound/config.h"
#include "libunbound/sldns/pkthdr.h"
#include "libunbound/sldns/sbuffer.h"
#include "libunbound/sldns/str2wire.h"
#include "libunbound/sldns/wire2str.h"
#include "libunbound/util/alloc.h"
#include "libunbound/util/net_help.h"
#include "libunbound/util/regional.h"
#include "libunbound/util/data/dname.h"
#include "libunbound/util/data/msgencode.h"
#include "libunbound/util/data/msgparse.h"
#include "libunbound/util/data/msgreply.h"

#include "log.h"
#include "unwind.h"
#include "frontend.h"
#include "control.h"
#include "dns64_synth.h"

#define MINIMUM(a, b)           (((a) < (b)) ? (a) : (b))
#define ROUTE_SOCKET_BUF_SIZE   16384

/*
 * size of a resource record with name a two octed pointer to qname
 * 2 octets pointer to qname
 * 2 octets TYPE
 * 2 octets CLASS
 * 4 octets TTL
 * 2 octets RDLENGTH
 */
#define COMPRESSED_RR_SIZE      12
#define MINIMIZE_ANSWER         1

#define FD_RESERVE              5
#define TCP_TIMEOUT             15
#define DEFAULT_TCP_SIZE        512

struct udp_ev {
        struct event             ev;
        uint8_t                  query[65536];
        struct msghdr            rcvmhdr;
        struct iovec             rcviov[1];
        struct sockaddr_storage  from;
} udp4ev, udp6ev;

struct tcp_accept_ev {
        struct event             ev;
        struct event             pause;
} tcp4ev, tcp6ev;

struct pending_query {
        TAILQ_ENTRY(pending_query)       entry;
        struct sockaddr_storage          from;
        struct sldns_buffer             *qbuf;
        struct sldns_buffer             *abuf;
        struct regional                 *region;
        struct query_info                qinfo;
        struct edns_data                 edns;
        struct event                     ev;            /* for tcp */
        struct event                     resp_ev;       /* for tcp */
        struct event                     tmo_ev;        /* for tcp */
        uint64_t                         imsg_id;
        uint16_t                         id;
        uint16_t                         flags;
        int                              fd;
        int                              tcp;
        int                              dns64_synthesize;
};

TAILQ_HEAD(, pending_query)      pending_queries;

struct bl_node {
        RB_ENTRY(bl_node)        entry;
        char                    *domain;
        int                      len;
        int                      wildcard;
};

__dead void              frontend_shutdown(void);
void                     frontend_sig_handler(int, short, void *);
void                     frontend_startup(void);
void                     udp_receive(int, short, void *);
void                     handle_query(struct pending_query *);
void                     free_pending_query(struct pending_query *);
void                     tcp_accept(int, short, void *);
int                      accept_reserve(int, struct sockaddr *, socklen_t *);
void                     accept_paused(int, short, void *);
void                     tcp_request(int, short, void *);
void                     tcp_response(int, short, void *);
void                     tcp_timeout(int, short, void *);
int                      check_query(sldns_buffer*);
void                     noerror_answer(struct pending_query *);
void                     synthesize_dns64_answer(struct pending_query *);
void                     resend_dns64_query(struct pending_query *);
void                     chaos_answer(struct pending_query *);
void                     error_answer(struct pending_query *, int rcode);
void                     send_answer(struct pending_query *);
void                     route_receive(int, short, void *);
void                     handle_route_message(struct rt_msghdr *,
                             struct sockaddr **);
void                     get_rtaddrs(int, struct sockaddr *,
                             struct sockaddr **);
struct pending_query    *find_pending_query(uint64_t);
void                     parse_trust_anchor(struct trust_anchor_head *, int);
void                     send_trust_anchors(struct trust_anchor_head *);
void                     write_trust_anchors(struct trust_anchor_head *, int);
void                     parse_blocklist(int);
int                      bl_cmp(struct bl_node *, struct bl_node *);
void                     free_bl(void);
int                      pending_query_cnt(void);
void                     check_available_af(void);
void                     reverse(char *, char *);

struct uw_conf          *frontend_conf;
static struct imsgev    *iev_main;
static struct imsgev    *iev_resolver;
struct event             ev_route;
int                      udp4sock = -1, udp6sock = -1;
int                      tcp4sock = -1, tcp6sock = -1;
int                      ta_fd = -1;

static struct trust_anchor_head  trust_anchors, new_trust_anchors;

RB_HEAD(bl_tree, bl_node)        bl_head = RB_INITIALIZER(&bl_head);
RB_PROTOTYPE(bl_tree, bl_node, entry, bl_cmp)
RB_GENERATE(bl_tree, bl_node, entry, bl_cmp)

struct dns64_prefix     *dns64_prefixes;
int                      dns64_prefix_count;

void
frontend_sig_handler(int sig, short event, void *bula)
{
        /*
         * Normal signal handler rules don't apply because libevent
         * decouples for us.
         */

        switch (sig) {
        case SIGINT:
        case SIGTERM:
                frontend_shutdown();
        default:
                fatalx("unexpected signal");
        }
}

void
frontend(int debug, int verbose)
{
        struct event     ev_sigint, ev_sigterm;
        struct passwd   *pw;

        frontend_conf = config_new_empty();

        log_init(debug, LOG_DAEMON);
        log_setverbose(verbose);

        if ((pw = getpwnam(UNWIND_USER)) == NULL)
                fatal("getpwnam");

        if (chroot(pw->pw_dir) == -1)
                fatal("chroot");
        if (chdir("/") == -1)
                fatal("chdir(\"/\")");

        setproctitle("%s", "frontend");
        log_procinit("frontend");

        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 (pledge("stdio dns unix recvfd", NULL) == -1)
                fatal("pledge");

        event_init();

        /* Setup signal handler. */
        signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL);
        signal_set(&ev_sigterm, SIGTERM, frontend_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 parent process. */
        if (iev_main != NULL)
                fatal("iev_main");
        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 = frontend_dispatch_main;
        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);

        udp4ev.rcviov[0].iov_base = (caddr_t)udp4ev.query;
        udp4ev.rcviov[0].iov_len = sizeof(udp4ev.query);
        udp4ev.rcvmhdr.msg_name = (caddr_t)&udp4ev.from;
        udp4ev.rcvmhdr.msg_namelen = sizeof(udp4ev.from);
        udp4ev.rcvmhdr.msg_iov = udp4ev.rcviov;
        udp4ev.rcvmhdr.msg_iovlen = 1;

        udp6ev.rcviov[0].iov_base = (caddr_t)udp6ev.query;
        udp6ev.rcviov[0].iov_len = sizeof(udp6ev.query);
        udp6ev.rcvmhdr.msg_name = (caddr_t)&udp6ev.from;
        udp6ev.rcvmhdr.msg_namelen = sizeof(udp6ev.from);
        udp6ev.rcvmhdr.msg_iov = udp6ev.rcviov;
        udp6ev.rcvmhdr.msg_iovlen = 1;

        TAILQ_INIT(&pending_queries);

        TAILQ_INIT(&trust_anchors);
        TAILQ_INIT(&new_trust_anchors);

        add_new_ta(&trust_anchors, KSK2017);
        add_new_ta(&trust_anchors, KSK2024);

        event_dispatch();

        frontend_shutdown();
}

__dead void
frontend_shutdown(void)
{
        /* Close pipes. */
        imsgbuf_write(&iev_resolver->ibuf);
        imsgbuf_clear(&iev_resolver->ibuf);
        close(iev_resolver->ibuf.fd);
        imsgbuf_write(&iev_main->ibuf);
        imsgbuf_clear(&iev_main->ibuf);
        close(iev_main->ibuf.fd);

        config_clear(frontend_conf);

        free(iev_resolver);
        free(iev_main);

        log_info("frontend exiting");
        exit(0);
}

int
frontend_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
frontend_imsg_compose_resolver(int type, pid_t pid, void *data,
    uint16_t datalen)
{
        return (imsg_compose_event(iev_resolver, type, 0, pid, -1, data,
            datalen));
}

void
frontend_dispatch_main(int fd, short event, void *bula)
{
        static struct uw_conf   *nconf;
        struct imsg              imsg;
        struct imsgev           *iev = bula;
        struct imsgbuf          *ibuf = &iev->ibuf;
        int                      n, shut = 0;

        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_RESOLVER:
                        /*
                         * Setup pipe and event handler to the resolver
                         * process.
                         */
                        if (iev_resolver) {
                                fatalx("%s: received unexpected imsg fd "
                                    "to frontend", __func__);
                                break;
                        }
                        if ((fd = imsg_get_fd(&imsg)) == -1) {
                                fatalx("%s: expected to receive imsg fd to "
                                   "frontend but didn't receive any",
                                   __func__);
                                break;
                        }

                        if (iev_resolver != NULL)
                                fatal("iev_resolver");
                        iev_resolver = malloc(sizeof(struct imsgev));
                        if (iev_resolver == NULL)
                                fatal(NULL);

                        if (imsgbuf_init(&iev_resolver->ibuf, fd) == -1)
                                fatal(NULL);
                        iev_resolver->handler = frontend_dispatch_resolver;
                        iev_resolver->events = EV_READ;

                        event_set(&iev_resolver->ev, iev_resolver->ibuf.fd,
                            iev_resolver->events, iev_resolver->handler,
                            iev_resolver);
                        event_add(&iev_resolver->ev, NULL);
                        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__);
                        merge_config(frontend_conf, nconf);
                        if (frontend_conf->blocklist_file == NULL)
                                free_bl();
                        nconf = NULL;
                        break;
                case IMSG_UDP6SOCK:
                        if (udp6sock != -1)
                                fatalx("%s: received unexpected udp6sock",
                                    __func__);
                        if ((udp6sock = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg "
                                    "UDP6 fd but didn't receive any", __func__);
                        event_set(&udp6ev.ev, udp6sock, EV_READ | EV_PERSIST,
                            udp_receive, &udp6ev);
                        event_add(&udp6ev.ev, NULL);
                        break;
                case IMSG_UDP4SOCK:
                        if (udp4sock != -1)
                                fatalx("%s: received unexpected udp4sock",
                                    __func__);
                        if ((udp4sock = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg "
                                    "UDP4 fd but didn't receive any", __func__);
                        event_set(&udp4ev.ev, udp4sock, EV_READ | EV_PERSIST,
                            udp_receive, &udp4ev);
                        event_add(&udp4ev.ev, NULL);
                        break;
                case IMSG_TCP4SOCK:
                        if (tcp4sock != -1)
                                fatalx("%s: received unexpected tcp4sock",
                                    __func__);
                        if ((tcp4sock = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg "
                                    "TCP4 fd but didn't receive any", __func__);
                        event_set(&tcp4ev.ev, tcp4sock, EV_READ | EV_PERSIST,
                            tcp_accept, &tcp4ev);
                        event_add(&tcp4ev.ev, NULL);
                        evtimer_set(&tcp4ev.pause, accept_paused, &tcp4ev);
                        break;
                case IMSG_TCP6SOCK:
                        if (tcp6sock != -1)
                                fatalx("%s: received unexpected tcp6sock",
                                    __func__);
                        if ((tcp6sock = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg "
                                    "TCP6 fd but didn't receive any", __func__);
                        event_set(&tcp6ev.ev, tcp6sock, EV_READ | EV_PERSIST,
                            tcp_accept, &tcp6ev);
                        event_add(&tcp6ev.ev, NULL);
                        evtimer_set(&tcp6ev.pause, accept_paused, &tcp6ev);
                        break;
                case IMSG_ROUTESOCK: {
                        static int       routesock = -1;

                        if (routesock != -1)
                                fatalx("%s: received unexpected routesock",
                                    __func__);
                        if ((fd = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg "
                                    "routesocket fd but didn't receive any",
                                    __func__);
                        routesock = fd;
                        event_set(&ev_route, fd, EV_READ | EV_PERSIST,
                            route_receive, NULL);
                        break;
                }
                case IMSG_STARTUP:
                        frontend_startup();
                        break;
                case IMSG_CONTROLFD:
                        if ((fd = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg control "
                                    "fd but didn't receive any", __func__);
                        /* Listen on control socket. */
                        control_listen(fd);
                        break;
                case IMSG_TAFD:
                        if ((ta_fd = imsg_get_fd(&imsg)) != -1)
                                parse_trust_anchor(&trust_anchors, ta_fd);
                        if (!TAILQ_EMPTY(&trust_anchors))
                                send_trust_anchors(&trust_anchors);
                        break;
                case IMSG_BLFD:
                        if ((fd = imsg_get_fd(&imsg)) == -1)
                                fatalx("%s: expected to receive imsg block "
                                   "list fd but didn't receive any", __func__);
                        parse_blocklist(fd);
                        break;
                default:
                        log_debug("%s: error handling 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
frontend_dispatch_resolver(int fd, short event, void *bula)
{
        static struct dns64_prefix      *new_dns64_prefixes = NULL;
        static int                       new_dns64_prefix_count = 0;
        static int                       new_dns64_prefix_pos = 0;
        struct pending_query            *pq;
        struct imsgev                   *iev = bula;
        struct imsgbuf                  *ibuf = &iev->ibuf;
        struct imsg                      imsg;
        int                              n, shut = 0, chg;

        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_ANSWER: {
                        struct answer_header    *answer_header;
                        int                      data_len;
                        uint8_t                 *data;

                        if (IMSG_DATA_SIZE(imsg) < sizeof(*answer_header))
                                fatalx("%s: IMSG_ANSWER wrong length: "
                                    "%lu", __func__, IMSG_DATA_SIZE(imsg));
                        answer_header = (struct answer_header *)imsg.data;
                        data = (uint8_t *)imsg.data + sizeof(*answer_header);
                        if (answer_header->answer_len > UINT16_MAX)
                                fatalx("%s: IMSG_ANSWER answer too big: %d",
                                    __func__, answer_header->answer_len);
                        data_len = IMSG_DATA_SIZE(imsg) -
                            sizeof(*answer_header);

                        if ((pq = find_pending_query(answer_header->id)) ==
                            NULL) {
                                log_warnx("%s: cannot find pending query %llu",
                                    __func__, answer_header->id);
                                break;
                        }

                        if (answer_header->srvfail) {
                                error_answer(pq, LDNS_RCODE_SERVFAIL);
                                send_answer(pq);
                                break;
                        }

                        if (answer_header->bogus && !(pq->flags & BIT_CD)) {
                                error_answer(pq, LDNS_RCODE_SERVFAIL);
                                send_answer(pq);
                                break;
                        }

                        if (sldns_buffer_position(pq->abuf) == 0 &&
                            !sldns_buffer_set_capacity(pq->abuf,
                            answer_header->answer_len)) {
                                error_answer(pq, LDNS_RCODE_SERVFAIL);
                                send_answer(pq);
                                break;
                        }

                        if (sldns_buffer_position(pq->abuf) + data_len >
                            sldns_buffer_capacity(pq->abuf))
                                fatalx("%s: IMSG_ANSWER answer too big: %d",
                                    __func__, data_len);
                        sldns_buffer_write(pq->abuf, data, data_len);

                        if (sldns_buffer_position(pq->abuf) ==
                            sldns_buffer_capacity(pq->abuf)) {
                                sldns_buffer_flip(pq->abuf);
                                if (pq->dns64_synthesize) {
                                        synthesize_dns64_answer(pq);
                                        send_answer(pq);
                                } else {
                                        noerror_answer(pq);
                                        if (pq->dns64_synthesize)
                                                /* we did not find a answer */
                                                resend_dns64_query(pq);
                                        else
                                                send_answer(pq);
                                }
                        }
                        break;
                }
                case IMSG_CTL_RESOLVER_INFO:
                case IMSG_CTL_AUTOCONF_RESOLVER_INFO:
                case IMSG_CTL_MEM_INFO:
                case IMSG_CTL_END:
                        control_imsg_relay(&imsg);
                        break;
                case IMSG_NEW_TA:
                        /* make sure this is a string */
                        ((char *)imsg.data)[IMSG_DATA_SIZE(imsg) - 1] = '\0';
                        add_new_ta(&new_trust_anchors, imsg.data);
                        break;
                case IMSG_NEW_TAS_ABORT:
                        free_tas(&new_trust_anchors);
                        break;
                case IMSG_NEW_TAS_DONE:
                        chg = merge_tas(&new_trust_anchors, &trust_anchors);
                        if (chg)
                                send_trust_anchors(&trust_anchors);

                        /*
                         * always write trust anchors, the modify date on
                         * the file is an indication when we made progress
                         */
                        if (ta_fd != -1)
                                write_trust_anchors(&trust_anchors, ta_fd);
                        break;
                case IMSG_NEW_DNS64_PREFIXES_START:
                        if (IMSG_DATA_SIZE(imsg) !=
                            sizeof(new_dns64_prefix_count))
                                fatalx("%s: IMSG_NEW_DNS64_PREFIXES_START "
                                    "wrong length: %lu", __func__,
                                    IMSG_DATA_SIZE(imsg));
                        memcpy(&new_dns64_prefix_count, imsg.data,
                            sizeof(new_dns64_prefix_count));
                        free(new_dns64_prefixes);
                        new_dns64_prefixes = NULL;
                        if (new_dns64_prefix_count > 0)
                                new_dns64_prefixes =
                                    calloc(new_dns64_prefix_count,
                                    sizeof(struct dns64_prefix));
                        new_dns64_prefix_pos = 0;
                        break;
                case IMSG_NEW_DNS64_PREFIX: {
                        if (IMSG_DATA_SIZE(imsg) != sizeof(struct dns64_prefix))
                                fatalx("%s: IMSG_NEW_DNS64_PREFIX wrong "
                                    "length: %lu", __func__,
                                    IMSG_DATA_SIZE(imsg));
                        if (new_dns64_prefixes == NULL)
                                break;
                        if (new_dns64_prefix_pos >= new_dns64_prefix_count)
                                fatalx("%s: IMSG_NEW_DNS64_PREFIX: too many "
                                    "prefixes", __func__);
                        memcpy(&new_dns64_prefixes[new_dns64_prefix_pos++],
                            imsg.data, sizeof(struct dns64_prefix));
                        break;
                }
                case IMSG_NEW_DNS64_PREFIXES_DONE:
                        free(dns64_prefixes);
                        dns64_prefixes = new_dns64_prefixes;
                        dns64_prefix_count = new_dns64_prefix_count;
                        new_dns64_prefixes = NULL;
                        break;
                default:
                        log_debug("%s: error handling 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
frontend_startup(void)
{
        if (!event_initialized(&ev_route))
                fatalx("%s: did not receive a route socket from the main "
                    "process", __func__);

        event_add(&ev_route, NULL);

        frontend_imsg_compose_main(IMSG_STARTUP_DONE, 0, NULL, 0);
        check_available_af();
}

void
free_pending_query(struct pending_query *pq)
{
        if (!pq)
                return;

        TAILQ_REMOVE(&pending_queries, pq, entry);
        regional_destroy(pq->region);
        sldns_buffer_free(pq->qbuf);
        sldns_buffer_free(pq->abuf);
        if (pq->tcp) {
                if (event_initialized(&pq->ev))
                        event_del(&pq->ev);
                if (event_initialized(&pq->resp_ev))
                        event_del(&pq->resp_ev);
                if (event_initialized(&pq->tmo_ev))
                        event_del(&pq->tmo_ev);
                if (pq->fd != -1)
                        close(pq->fd);
        }
        free(pq);
}

void
udp_receive(int fd, short events, void *arg)
{
        struct udp_ev           *udpev = (struct udp_ev *)arg;
        struct pending_query    *pq = NULL;
        ssize_t                  len;

        if ((len = recvmsg(fd, &udpev->rcvmhdr, 0)) == -1) {
                log_warn("recvmsg");
                return;
        }

        if ((pq = calloc(1, sizeof(*pq))) == NULL) {
                log_warn(NULL);
                return;
        }

        do {
                arc4random_buf(&pq->imsg_id, sizeof(pq->imsg_id));
        } while(find_pending_query(pq->imsg_id) != NULL);

        TAILQ_INSERT_TAIL(&pending_queries, pq, entry);

        pq->from = udpev->from;
        pq->fd = fd;
        pq->qbuf = sldns_buffer_new(len);
        pq->abuf = sldns_buffer_new(len); /* make sure we can send errors */
        pq->region = regional_create();

        if (!pq->qbuf || !pq->abuf || !pq->region) {
                log_warnx("out of memory");
                free_pending_query(pq);
                return;
        }

        sldns_buffer_write(pq->qbuf, udpev->query, len);
        sldns_buffer_flip(pq->qbuf);
        handle_query(pq);
}

void
handle_query(struct pending_query *pq)
{
        struct query_imsg        query_imsg;
        struct bl_node           find;
        int                      rcode, matched;
        char                    *str;
        char                     dname[LDNS_MAX_DOMAINLEN + 1];
        char                     qclass_buf[16];
        char                     qtype_buf[16];

        memset(&query_imsg, 0, sizeof (query_imsg));

        if (log_getverbose() & OPT_VERBOSE2 && (str =
            sldns_wire2str_pkt(sldns_buffer_begin(pq->qbuf),
            sldns_buffer_limit(pq->qbuf))) != NULL) {
                log_debug("from: %s\n%s", ip_port((struct sockaddr *)
                    &pq->from), str);
                free(str);
        }

        if (sldns_buffer_remaining(pq->qbuf) < LDNS_HEADER_SIZE) {
                log_warnx("bad query: too short, dropped");
                goto drop;
        }

        pq->id = sldns_buffer_read_u16_at(pq->qbuf, 0);
        pq->flags = sldns_buffer_read_u16_at(pq->qbuf, 2);

        if (!query_info_parse(&pq->qinfo, pq->qbuf)) {
                log_warnx("query_info_parse failed");
                goto drop;
        }

        rcode = check_query(pq->qbuf);
        switch (rcode) {
        case LDNS_RCODE_NOERROR:
                break;
        case -1:
                goto drop;
        default:
                error_answer(pq, rcode);
                goto send_answer;
        }

        rcode = parse_edns_from_query_pkt(pq->qbuf, &pq->edns, NULL, NULL,
            NULL, 0, pq->region, NULL);
        if (rcode != LDNS_RCODE_NOERROR) {
                error_answer(pq, rcode);
                goto send_answer;
        }

        if (!dname_valid(pq->qinfo.qname, pq->qinfo.qname_len)) {
                error_answer(pq, LDNS_RCODE_FORMERR);
                goto send_answer;
        }
        dname_str(pq->qinfo.qname, dname);

        sldns_wire2str_class_buf(pq->qinfo.qclass, qclass_buf,
            sizeof(qclass_buf));
        sldns_wire2str_type_buf(pq->qinfo.qtype, qtype_buf, sizeof(qtype_buf));
        log_debug("%s: %s %s %s ?", ip_port((struct sockaddr *)&pq->from),
            dname, qclass_buf, qtype_buf);

        if (!RB_EMPTY(&bl_head)) {
                find.len = strlen(dname);
                find.wildcard = 0;
                reverse(dname, dname + find.len);
                find.domain = dname;
                matched = (RB_FIND(bl_tree, &bl_head, &find) != NULL);
                reverse(dname, dname + find.len);
                if (matched) {
                        if (frontend_conf->blocklist_log)
                                log_info("blocking %s", dname);
                        error_answer(pq, LDNS_RCODE_REFUSED);
                        goto send_answer;
                }
        }

        if (pq->qinfo.qtype == LDNS_RR_TYPE_AXFR || pq->qinfo.qtype ==
            LDNS_RR_TYPE_IXFR) {
                error_answer(pq, LDNS_RCODE_REFUSED);
                goto send_answer;
        }

        if(pq->qinfo.qtype == LDNS_RR_TYPE_OPT ||
            pq->qinfo.qtype == LDNS_RR_TYPE_TSIG ||
            pq->qinfo.qtype == LDNS_RR_TYPE_TKEY ||
            pq->qinfo.qtype == LDNS_RR_TYPE_MAILA ||
            pq->qinfo.qtype == LDNS_RR_TYPE_MAILB ||
            (pq->qinfo.qtype >= 128 && pq->qinfo.qtype <= 248)) {
                error_answer(pq, LDNS_RCODE_FORMERR);
                goto send_answer;
        }

        if (pq->qinfo.qclass == LDNS_RR_CLASS_CH) {
                if (strcasecmp(dname, "version.server.") == 0 ||
                    strcasecmp(dname, "version.bind.") == 0) {
                        chaos_answer(pq);
                } else
                        error_answer(pq, LDNS_RCODE_REFUSED);
                goto send_answer;
        }

        if (strlcpy(query_imsg.qname, dname, sizeof(query_imsg.qname)) >=
            sizeof(query_imsg.qname)) {
                log_warnx("qname too long");
                error_answer(pq, LDNS_RCODE_FORMERR);
                goto send_answer;
        }
        query_imsg.id = pq->imsg_id;
        query_imsg.t = pq->qinfo.qtype;
        query_imsg.c = pq->qinfo.qclass;

        if (frontend_imsg_compose_resolver(IMSG_QUERY, 0, &query_imsg,
            sizeof(query_imsg)) == -1) {
                error_answer(pq, LDNS_RCODE_SERVFAIL);
                goto send_answer;
        }
        return;

 send_answer:
        send_answer(pq);
        return;

 drop:
        free_pending_query(pq);
}

void
noerror_answer(struct pending_query *pq)
{
        struct query_info                skip, qinfo;
        struct reply_info               *rinfo = NULL;
        struct alloc_cache               alloc;
        struct edns_data                 edns;
        struct ub_packed_rrset_key      *an_rrset = NULL;
        struct packed_rrset_data        *an_rrset_data = NULL;

        alloc_init(&alloc, NULL, 0);
        memset(&qinfo, 0, sizeof(qinfo));
        /* read past query section, no memory is allocated */
        if (!query_info_parse(&skip, pq->abuf))
                goto srvfail;

        if (reply_info_parse(pq->abuf, &alloc, &qinfo, &rinfo, pq->region,
            &edns) != 0)
                goto srvfail;

        if ((an_rrset = reply_find_answer_rrset(&qinfo, rinfo)) != NULL)
                an_rrset_data = (struct packed_rrset_data*)an_rrset->entry.data;

        /* reply_info_parse() allocates memory */
        query_info_clear(&qinfo);

        /* XXX check that there a no AAAA records in answer section? */
        if ((an_rrset_data == NULL || an_rrset_data->count == 0) &&
            !pq->dns64_synthesize && pq->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
            pq->qinfo.qclass == LDNS_RR_CLASS_IN && dns64_prefix_count > 0) {
                pq->dns64_synthesize = 1;
                return;
        }

        sldns_buffer_clear(pq->abuf);
        if (reply_info_encode(&pq->qinfo, rinfo, htons(pq->id), rinfo->flags,
            pq->abuf, 0, pq->region, pq->tcp ? UINT16_MAX : pq->edns.udp_size,
            pq->edns.bits & EDNS_DO, MINIMIZE_ANSWER) == 0)
                goto srvfail;

        reply_info_parsedelete(rinfo, &alloc);
        alloc_clear(&alloc);
        return;

 srvfail:
        reply_info_parsedelete(rinfo, &alloc);
        alloc_clear(&alloc);
        error_answer(pq, LDNS_RCODE_SERVFAIL);
}

void
synthesize_dns64_answer(struct pending_query *pq)
{
        struct query_info                skip, qinfo;
        struct reply_info               *rinfo = NULL, *synth_rinfo = NULL;
        struct alloc_cache               alloc;
        struct edns_data                 edns;
        size_t                           i;

        pq->dns64_synthesize = 0;

        alloc_init(&alloc, NULL, 0);
        memset(&qinfo, 0, sizeof(qinfo));
        /* read past query section, no memory is allocated */
        if (!query_info_parse(&skip, pq->abuf))
                goto srvfail;

        if (reply_info_parse(pq->abuf, &alloc, &qinfo, &rinfo, pq->region,
            &edns) != 0)
                goto srvfail;

        /* reply_info_parse() allocates memory */
        query_info_clear(&qinfo);

        synth_rinfo = construct_reply_info_base(pq->region, rinfo->flags,
            rinfo->qdcount, rinfo->ttl, rinfo->prefetch_ttl,
            rinfo->serve_expired_ttl, rinfo->serve_expired_norec_ttl,
            rinfo->an_numrrsets, rinfo->ns_numrrsets, rinfo->ar_numrrsets,
            rinfo->rrset_count, rinfo->security, rinfo->reason_bogus);

        if (!synth_rinfo)
                goto srvfail;

        if(!reply_info_alloc_rrset_keys(synth_rinfo, NULL, pq->region))
                goto srvfail;

        for (i = 0; i < synth_rinfo->rrset_count; i++) {
                struct ub_packed_rrset_key      *src_rrset_key, *dst_rrset_key;
                struct packed_rrset_data        *src_rrset_data;
                struct packed_rrset_data        *dst_rrset_data;

                src_rrset_key = rinfo->rrsets[i];
                src_rrset_data =
                    (struct packed_rrset_data *)src_rrset_key->entry.data;
                dst_rrset_key = synth_rinfo->rrsets[i];

                dst_rrset_key->id = src_rrset_key->id;
                dst_rrset_key->rk = src_rrset_key->rk;

                if (i < rinfo->an_numrrsets && src_rrset_key->rk.type ==
                    htons(LDNS_RR_TYPE_A)) {
                        dns64_synth_aaaa_data(src_rrset_key, src_rrset_data,
                            dst_rrset_key, &dst_rrset_data, pq->region);
                        if (dst_rrset_data == NULL)
                                goto srvfail;
                } else {
                        dst_rrset_key->entry.hash = src_rrset_key->entry.hash;
                        dst_rrset_key->rk.dname = regional_alloc_init(
                            pq->region, src_rrset_key->rk.dname,
                            src_rrset_key->rk.dname_len);
                        if (dst_rrset_key->rk.dname == NULL)
                                goto srvfail;

                        dst_rrset_data = regional_alloc_init(pq->region,
                            src_rrset_data,
                            packed_rrset_sizeof(src_rrset_data));
                        if (dst_rrset_data == NULL)
                                goto srvfail;
                }

                packed_rrset_ptr_fixup(dst_rrset_data);
                dst_rrset_key->entry.data = dst_rrset_data;
        }

        if (!sldns_buffer_set_capacity(pq->abuf, pq->tcp ? UINT16_MAX :
            pq->edns.udp_size))
                goto srvfail;

        sldns_buffer_clear(pq->abuf);

        if (reply_info_encode(&pq->qinfo, synth_rinfo, htons(pq->id),
            synth_rinfo->flags, pq->abuf, 0, pq->region,
            pq->tcp ? UINT16_MAX : pq->edns.udp_size,
            pq->edns.bits & EDNS_DO, MINIMIZE_ANSWER) == 0)
                goto srvfail;

        reply_info_parsedelete(rinfo, &alloc);
        alloc_clear(&alloc);
        return;

 srvfail:
        reply_info_parsedelete(rinfo, &alloc);
        alloc_clear(&alloc);
        error_answer(pq, LDNS_RCODE_SERVFAIL);
}

void
resend_dns64_query(struct pending_query *opq)
{
        struct pending_query    *pq;
        struct query_imsg        query_imsg;
        int                      rcode;
        char                     dname[LDNS_MAX_DOMAINLEN + 1];

        if ((pq = calloc(1, sizeof(*pq))) == NULL) {
                log_warn(NULL);
                return;
        }

        do {
                arc4random_buf(&pq->imsg_id, sizeof(pq->imsg_id));
        } while(find_pending_query(pq->imsg_id) != NULL);

        TAILQ_INSERT_TAIL(&pending_queries, pq, entry);

        pq->from = opq->from;
        pq->fd = opq->fd;
        opq->fd = -1;
        pq->tcp = opq->tcp;
        pq->qbuf = sldns_buffer_new(sldns_buffer_capacity(opq->qbuf));
        pq->abuf = sldns_buffer_new(sldns_buffer_capacity(opq->abuf));
        pq->region = regional_create();

        if (!pq->qbuf || !pq->abuf || !pq->region) {
                log_warnx("out of memory");
                free_pending_query(pq);
                free_pending_query(opq);
                return;
        }

        sldns_buffer_rewind(opq->qbuf);
        sldns_buffer_write(pq->qbuf, sldns_buffer_current(opq->qbuf),
            sldns_buffer_remaining(opq->qbuf));
        sldns_buffer_flip(pq->qbuf);

        if (pq->tcp) {
                struct timeval   timeout = {TCP_TIMEOUT, 0};

                event_set(&pq->ev, pq->fd, EV_READ | EV_PERSIST, tcp_request,
                    pq);
                event_set(&pq->resp_ev, pq->fd, EV_WRITE | EV_PERSIST,
                    tcp_response, pq);
                evtimer_set(&pq->tmo_ev, tcp_timeout, pq);
                evtimer_add(&pq->tmo_ev, &timeout);
        }

        if (sldns_buffer_remaining(pq->qbuf) < LDNS_HEADER_SIZE) {
                log_warnx("bad query: too short, dropped");
                goto drop;
        }

        pq->id = sldns_buffer_read_u16_at(pq->qbuf, 0);
        pq->flags = sldns_buffer_read_u16_at(pq->qbuf, 2);

        if (!query_info_parse(&pq->qinfo, pq->qbuf)) {
                log_warnx("query_info_parse failed");
                goto drop;
        }

        rcode = parse_edns_from_query_pkt(pq->qbuf, &pq->edns, NULL, NULL,
            NULL, 0, pq->region, NULL);
        if (rcode != LDNS_RCODE_NOERROR) {
                error_answer(pq, rcode);
                goto send_answer;
        }

        dname_str(pq->qinfo.qname, dname);
        strlcpy(query_imsg.qname, dname, sizeof(query_imsg.qname));
        query_imsg.id = pq->imsg_id;
        query_imsg.t = LDNS_RR_TYPE_A;
        query_imsg.c = pq->qinfo.qclass;

        pq->dns64_synthesize = 1;

        if (frontend_imsg_compose_resolver(IMSG_QUERY, 0, &query_imsg,
            sizeof(query_imsg)) == -1) {
                error_answer(pq, LDNS_RCODE_SERVFAIL);
                goto send_answer;
        }

        free_pending_query(opq);
        return;

 send_answer:
        free_pending_query(opq);
        send_answer(pq);
        return;

 drop:
        free_pending_query(opq);
        free_pending_query(pq);
}

void
chaos_answer(struct pending_query *pq)
{
        size_t           len;
        const char      *name = "unwind";

        len = strlen(name);
        if (!sldns_buffer_set_capacity(pq->abuf,
            sldns_buffer_capacity(pq->qbuf) + COMPRESSED_RR_SIZE + 1 + len)) {
                error_answer(pq, LDNS_RCODE_SERVFAIL);
                return;
        }

        sldns_buffer_copy(pq->abuf, pq->qbuf);

        sldns_buffer_clear(pq->abuf);

        sldns_buffer_skip(pq->abuf, sizeof(uint16_t));  /* skip id */
        sldns_buffer_write_u16(pq->abuf, 0);            /* clear flags */
        LDNS_QR_SET(sldns_buffer_begin(pq->abuf));
        LDNS_RA_SET(sldns_buffer_begin(pq->abuf));
        if (LDNS_RD_WIRE(sldns_buffer_begin(pq->qbuf)))
                LDNS_RD_SET(sldns_buffer_begin(pq->abuf));
        if (LDNS_CD_WIRE(sldns_buffer_begin(pq->qbuf)))
                LDNS_CD_SET(sldns_buffer_begin(pq->abuf));
        LDNS_RCODE_SET(sldns_buffer_begin(pq->abuf), LDNS_RCODE_NOERROR);
        sldns_buffer_write_u16(pq->abuf, 1);            /* qdcount */
        sldns_buffer_write_u16(pq->abuf, 1);            /* ancount */
        sldns_buffer_write_u16(pq->abuf, 0);            /* nscount */
        sldns_buffer_write_u16(pq->abuf, 0);            /* arcount */
        (void)query_dname_len(pq->abuf);                /* skip qname */
        sldns_buffer_skip(pq->abuf, sizeof(uint16_t));  /* skip qtype */
        sldns_buffer_skip(pq->abuf, sizeof(uint16_t));  /* skip qclass */

        sldns_buffer_write_u16(pq->abuf, 0xc00c);       /* ptr to query */
        sldns_buffer_write_u16(pq->abuf, LDNS_RR_TYPE_TXT);
        sldns_buffer_write_u16(pq->abuf, LDNS_RR_CLASS_CH);
        sldns_buffer_write_u32(pq->abuf, 0);            /* TTL */
        sldns_buffer_write_u16(pq->abuf, 1 + len);      /* RDLENGTH */
        sldns_buffer_write_u8(pq->abuf, len);           /* length octed */
        sldns_buffer_write(pq->abuf, name, len);
        sldns_buffer_flip(pq->abuf);
}

void
error_answer(struct pending_query *pq, int rcode)
{
        sldns_buffer_clear(pq->abuf);
        error_encode(pq->abuf, rcode, &pq->qinfo, htons(pq->id), pq->flags,
            pq->edns.edns_present ? &pq->edns : NULL);
}

int
check_query(sldns_buffer* pkt)
{
        if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) {
                log_warnx("bad query: too short, dropped");
                return -1;
        }
        if(LDNS_QR_WIRE(sldns_buffer_begin(pkt))) {
                log_warnx("bad query: QR set, dropped");
                return -1;
        }
        if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) {
                LDNS_TC_CLR(sldns_buffer_begin(pkt));
                log_warnx("bad query: TC set");
                return (LDNS_RCODE_FORMERR);
        }
        if(!(LDNS_RD_WIRE(sldns_buffer_begin(pkt)))) {
                log_warnx("bad query: RD not set");
                return (LDNS_RCODE_REFUSED);
        }
        if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) {
                log_warnx("bad query: unknown opcode %d",
                    LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)));
                return (LDNS_RCODE_NOTIMPL);
        }

        if (LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1 &&
            LDNS_ANCOUNT(sldns_buffer_begin(pkt))!= 0 &&
            LDNS_NSCOUNT(sldns_buffer_begin(pkt))!= 0 &&
            LDNS_ARCOUNT(sldns_buffer_begin(pkt)) > 1) {
                log_warnx("bad query: qdcount: %d, ancount: %d "
                    "nscount: %d, arcount: %d",
                    LDNS_QDCOUNT(sldns_buffer_begin(pkt)),
                    LDNS_ANCOUNT(sldns_buffer_begin(pkt)),
                    LDNS_NSCOUNT(sldns_buffer_begin(pkt)),
                    LDNS_ARCOUNT(sldns_buffer_begin(pkt)));
                return (LDNS_RCODE_FORMERR);
        }
        return (LDNS_RCODE_NOERROR);
}

void
send_answer(struct pending_query *pq)
{
        char    *str;

        if (log_getverbose() & OPT_VERBOSE2 && (str =
            sldns_wire2str_pkt(sldns_buffer_begin(pq->abuf),
            sldns_buffer_limit(pq->abuf))) != NULL) {
                log_debug("from: %s\n%s", ip_port((struct sockaddr *)
                    &pq->from), str);
                free(str);
        }

        if (!pq->tcp) {
                if(sendto(pq->fd, sldns_buffer_current(pq->abuf),
                    sldns_buffer_remaining(pq->abuf), 0,
                    (struct sockaddr *)&pq->from, pq->from.ss_len) == -1)
                        log_warn("sendto");
                free_pending_query(pq);
        } else {
                struct sldns_buffer     *tmp;

                tmp = sldns_buffer_new(sldns_buffer_limit(pq->abuf) + 2);

                if (!tmp) {
                        free_pending_query(pq);
                        return;
                }

                sldns_buffer_write_u16(tmp, sldns_buffer_limit(pq->abuf));
                sldns_buffer_write(tmp, sldns_buffer_current(pq->abuf),
                    sldns_buffer_remaining(pq->abuf));
                sldns_buffer_flip(tmp);
                sldns_buffer_free(pq->abuf);
                pq->abuf = tmp;
                event_add(&pq->resp_ev, NULL);
        }
}

char*
ip_port(struct sockaddr *sa)
{
        static char      hbuf[NI_MAXHOST], buf[NI_MAXHOST];

        if (getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0,
            NI_NUMERICHOST) != 0) {
                snprintf(buf, sizeof(buf), "%s", "(unknown)");
                return buf;
        }

        if (sa->sa_family == AF_INET6)
                snprintf(buf, sizeof(buf), "[%s]:%d", hbuf, ntohs(
                    ((struct sockaddr_in6 *)sa)->sin6_port));
        if (sa->sa_family == AF_INET)
                snprintf(buf, sizeof(buf), "[%s]:%d", hbuf, ntohs(
                    ((struct sockaddr_in *)sa)->sin_port));

        return buf;
}

struct pending_query*
find_pending_query(uint64_t id)
{
        struct pending_query    *pq;

        TAILQ_FOREACH(pq, &pending_queries, entry)
                if (pq->imsg_id == id)
                        return pq;
        return NULL;
}

void
route_receive(int fd, short events, void *arg)
{
        static uint8_t          *buf;

        struct rt_msghdr        *rtm;
        struct sockaddr         *sa, *rti_info[RTAX_MAX];
        ssize_t                  n;

        if (buf == NULL) {
                buf = malloc(ROUTE_SOCKET_BUF_SIZE);
                if (buf == NULL)
                        fatal("malloc");
        }
        rtm = (struct rt_msghdr *)buf;
        if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
                if (errno == EAGAIN || errno == EINTR)
                        return;
                log_warn("dispatch_rtmsg: read error");
                return;
        }

        if (n == 0)
                fatal("routing socket closed");

        if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
                log_warnx("partial rtm of %zd in buffer", n);
                return;
        }

        if (rtm->rtm_version != RTM_VERSION)
                return;

        sa = (struct sockaddr *)(buf + rtm->rtm_hdrlen);
        get_rtaddrs(rtm->rtm_addrs, sa, rti_info);

        handle_route_message(rtm, rti_info);
}

#define ROUNDUP(a) \
        ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))

void
get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
        int     i;

        for (i = 0; i < RTAX_MAX; i++) {
                if (addrs & (1 << i)) {
                        rti_info[i] = sa;
                        sa = (struct sockaddr *)((char *)(sa) +
                            ROUNDUP(sa->sa_len));
                } else
                        rti_info[i] = NULL;
        }
}

void
handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
{
        struct imsg_rdns_proposal        rdns_proposal;
        struct sockaddr_rtdns           *rtdns;
        struct if_announcemsghdr        *ifan;

        switch (rtm->rtm_type) {
        case RTM_IFANNOUNCE:
                ifan = (struct if_announcemsghdr *)rtm;
                if (ifan->ifan_what == IFAN_ARRIVAL)
                        break;
                rdns_proposal.if_index = ifan->ifan_index;
                rdns_proposal.src = 0;
                rdns_proposal.rtdns.sr_family = AF_INET;
                rdns_proposal.rtdns.sr_len = offsetof(struct sockaddr_rtdns,
                    sr_dns);
                frontend_imsg_compose_resolver(IMSG_REPLACE_DNS, 0,
                    &rdns_proposal, sizeof(rdns_proposal));
                break;
        case RTM_IFINFO:
                frontend_imsg_compose_resolver(IMSG_NETWORK_CHANGED, 0, NULL,
                    0);
                break;
        case RTM_PROPOSAL:
                if (!(rtm->rtm_addrs & RTA_DNS))
                        break;

                rtdns = (struct sockaddr_rtdns*)rti_info[RTAX_DNS];
                rdns_proposal.if_index = rtm->rtm_index;
                rdns_proposal.src = rtm->rtm_priority;
                memcpy(&rdns_proposal.rtdns, rtdns, sizeof(rdns_proposal.rtdns));
                frontend_imsg_compose_resolver(IMSG_REPLACE_DNS, 0,
                    &rdns_proposal, sizeof(rdns_proposal));
                break;
        case RTM_NEWADDR:
        case RTM_DELADDR:
        case RTM_DESYNC:
                check_available_af();
                break;
        default:
                break;
        }
}

void
add_new_ta(struct trust_anchor_head *tah, char *val)
{
        struct trust_anchor     *ta, *i;
        int                      cmp;

        if ((ta = malloc(sizeof(*ta))) == NULL)
                fatal("%s", __func__);
        if ((ta->ta = strdup(val)) == NULL)
                fatal("%s", __func__);

        /* keep the list sorted to prevent churn if the order changes in DNS */
        TAILQ_FOREACH(i, tah, entry) {
                cmp = strcmp(i->ta, ta->ta);
                if ( cmp == 0) {
                        /* duplicate */
                        free(ta->ta);
                        free(ta);
                        return;
                } else if (cmp > 0) {
                        TAILQ_INSERT_BEFORE(i, ta, entry);
                        return;
                }
        }
        TAILQ_INSERT_TAIL(tah, ta, entry);
}

void
free_tas(struct trust_anchor_head *tah)
{
        struct trust_anchor     *ta;

        while ((ta = TAILQ_FIRST(tah))) {
                TAILQ_REMOVE(tah, ta, entry);
                free(ta->ta);
                free(ta);
        }
}

int
merge_tas(struct trust_anchor_head *newh, struct trust_anchor_head *oldh)
{
        struct trust_anchor     *i, *j;
        int                      chg = 0;

        j = TAILQ_FIRST(oldh);

        TAILQ_FOREACH(i, newh, entry) {
                if (j == NULL || strcmp(i->ta, j->ta) != 0) {
                        chg = 1;
                        break;
                }
                j = TAILQ_NEXT(j, entry);
        }
        if (j != NULL)
                chg = 1;

        if (chg) {
                free_tas(oldh);
                TAILQ_CONCAT(oldh, newh, entry);
        } else {
                free_tas(newh);
        }
        return (chg);
}

void
parse_trust_anchor(struct trust_anchor_head *tah, int fd)
{
        size_t   len, dname_len;
        ssize_t  n, sz;
        uint8_t  rr[LDNS_RR_BUF_SIZE];
        char    *str, *p, buf[512], *line;

        sz = 0;
        str = NULL;

        while ((n = read(fd, buf, sizeof(buf))) > 0) {
                p = recallocarray(str, sz, sz + n, 1);
                if (p == NULL) {
                        log_warn("%s", __func__);
                        goto out;
                }
                str = p;
                memcpy(str + sz, buf, n);
                sz += n;
        }

        if (n == -1) {
                log_warn("%s", __func__);
                goto out;
        }

        /* make it a string */
        p = recallocarray(str, sz, sz + 1, 1);
        if (p == NULL) {
                log_warn("%s", __func__);
                goto out;
        }
        str = p;
        sz++;

        len = sizeof(rr);

        while ((line = strsep(&p, "\n")) != NULL) {
                if (sldns_str2wire_rr_buf(line, rr, &len, &dname_len,
                    ROOT_DNSKEY_TTL, NULL, 0, NULL, 0) != 0)
                        continue;
                if (sldns_wirerr_get_type(rr, len, dname_len) ==
                    LDNS_RR_TYPE_DNSKEY)
                        add_new_ta(tah, line);
        }

out:
        free(str);
        return;
}

void
send_trust_anchors(struct trust_anchor_head *tah)
{
        struct trust_anchor     *ta;

        TAILQ_FOREACH(ta, tah, entry)
                frontend_imsg_compose_resolver(IMSG_NEW_TA, 0, ta->ta,
                    strlen(ta->ta) + 1);
        frontend_imsg_compose_resolver(IMSG_NEW_TAS_DONE, 0, NULL, 0);
}

void
write_trust_anchors(struct trust_anchor_head *tah, int fd)
{
        struct trust_anchor     *ta;
        size_t                   len = 0;
        ssize_t                  n;
        char                    *str;

        if (lseek(fd, 0, SEEK_SET) == -1) {
                log_warn("%s", __func__);
                goto out;
        }

        TAILQ_FOREACH(ta, tah, entry) {
                if ((n = asprintf(&str, "%s\n", ta->ta)) == -1) {
                        log_warn("%s", __func__);
                        len = 0;
                        goto out;
                }
                len += n;
                if (write(fd, str, n) != n) {
                        log_warn("%s", __func__);
                        free(str);
                        len = 0;
                        goto out;
                }
                free(str);
        }
out:
        ftruncate(fd, len);
        fsync(fd);
}

void
parse_blocklist(int fd)
{
        FILE             *f;
        struct bl_node  *bl_node;
        char             *line = NULL;
        size_t            linesize = 0;
        ssize_t           linelen;

        if((f = fdopen(fd, "r")) == NULL) {
                log_warn("cannot read block list");
                close(fd);
                return;
        }

        free_bl();

        while ((linelen = getline(&line, &linesize, f)) != -1) {
                if (line[linelen - 1] == '\n') {
                        if (linelen >= 2 && line[linelen - 2] != '.')
                                line[linelen - 1] = '.';
                        else
                                line[linelen-- - 1] = '\0';
                }

                if (line[0] == '#')
                    continue;

                bl_node = malloc(sizeof *bl_node);
                if (bl_node == NULL)
                        fatal("%s: malloc", __func__);
                if ((bl_node->domain = strdup(line)) == NULL)
                        fatal("%s: strdup", __func__);
                reverse(bl_node->domain, bl_node->domain + linelen);
                bl_node->len = linelen;
                bl_node->wildcard = line[0] == '.';
                if (RB_INSERT(bl_tree, &bl_head, bl_node) != NULL) {
                        log_warnx("duplicate blocked domain \"%s\"", line);
                        free(bl_node->domain);
                        free(bl_node);
                }
        }
        free(line);
        if (ferror(f))
                log_warn("getline");
        fclose(f);
}

int
bl_cmp(struct bl_node *e1, struct bl_node *e2) {
        if (e1->wildcard == e2->wildcard)
                return (strcasecmp(e1->domain, e2->domain));
        else if (e1->wildcard)
                return (strncasecmp(e1->domain, e2->domain, e1->len));
        else /* e2->wildcard */
                return (strncasecmp(e1->domain, e2->domain, e2->len));
}

void
free_bl(void)
{
        struct bl_node  *n, *nxt;

        RB_FOREACH_SAFE(n, bl_tree, &bl_head, nxt) {
                RB_REMOVE(bl_tree, &bl_head, n);
                free(n->domain);
                free(n);
        }
}

int
pending_query_cnt(void)
{
        struct pending_query    *e;
        int                      cnt = 0;

        TAILQ_FOREACH(e, &pending_queries, entry)
                cnt++;
        return cnt;
}

void
accept_paused(int fd, short events, void *arg)
{
        struct tcp_accept_ev    *tcpev = arg;
        event_add(&tcpev->ev, NULL);
}

int
accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
        if (getdtablecount() + FD_RESERVE >= getdtablesize()) {
                log_debug("%s: inflight fds exceeded", __func__);
                errno = EMFILE;
                return -1;
        }
        return accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
}

void
tcp_accept(int fd, short events, void *arg)
{
        static struct timeval    timeout = {TCP_TIMEOUT, 0};
        static struct timeval    backoff = {1, 0};
        struct pending_query    *pq;
        struct tcp_accept_ev    *tcpev;
        struct sockaddr_storage  ss;
        socklen_t                len;
        int                      s;

        tcpev = arg;
        len = sizeof(ss);

        if ((s = accept_reserve(fd, (struct sockaddr *)&ss, &len)) == -1) {
                switch (errno) {
                case EINTR:
                case EWOULDBLOCK:
                case ECONNABORTED:
                        return;
                case EMFILE:
                case ENFILE:
                        event_del(&tcpev->ev);
                        evtimer_add(&tcpev->pause, &backoff);
                        return;
                default:
                        fatal("accept");
                }
        }

        if ((pq = calloc(1, sizeof(*pq))) == NULL) {
                log_warn(NULL);
                close(s);
                return;
        }

        do {
                arc4random_buf(&pq->imsg_id, sizeof(pq->imsg_id));
        } while(find_pending_query(pq->imsg_id) != NULL);

        TAILQ_INSERT_TAIL(&pending_queries, pq, entry);

        pq->from = ss;
        pq->fd = s;
        pq->tcp = 1;
        pq->qbuf = sldns_buffer_new(DEFAULT_TCP_SIZE);
        pq->region = regional_create();

        if (!pq->qbuf || !pq->region) {
                free_pending_query(pq);
                return;
        }

        event_set(&pq->ev, s, EV_READ | EV_PERSIST, tcp_request, pq);
        event_add(&pq->ev, NULL);
        event_set(&pq->resp_ev, s, EV_WRITE | EV_PERSIST, tcp_response, pq);

        evtimer_set(&pq->tmo_ev, tcp_timeout, pq);
        evtimer_add(&pq->tmo_ev, &timeout);
}

void
tcp_request(int fd, short events, void *arg)
{
        struct pending_query    *pq;
        ssize_t                  n;

        pq = arg;

        n = read(fd, sldns_buffer_current(pq->qbuf),
            sldns_buffer_remaining(pq->qbuf));

        switch (n) {
        case -1:
                switch (errno) {
                case EINTR:
                case EAGAIN:
                        return;
                default:
                        goto fail;
                }
                break;
        case 0:
                log_debug("closed connection");
                goto fail;
        default:
                break;
        }

        sldns_buffer_skip(pq->qbuf, n);

        if (sldns_buffer_position(pq->qbuf) >= 2 && !pq->abuf) {
                struct sldns_buffer     *tmp;
                size_t                   rem;
                uint16_t                 len;

                sldns_buffer_flip(pq->qbuf);
                len = sldns_buffer_read_u16(pq->qbuf);
                tmp = sldns_buffer_new(len);
                if (tmp == NULL)
                        goto fail;
                pq->abuf = sldns_buffer_new(len);
                if (pq->abuf == NULL) {
                        sldns_buffer_free(tmp);
                        goto fail;
                }

                rem = sldns_buffer_remaining(pq->qbuf);
                sldns_buffer_write(tmp, sldns_buffer_current(pq->qbuf),
                    MINIMUM(len, rem));
                sldns_buffer_free(pq->qbuf);
                pq->qbuf = tmp;
        }
        if (sldns_buffer_remaining(pq->qbuf) == 0) {
                sldns_buffer_flip(pq->qbuf);
                shutdown(fd, SHUT_RD);
                event_del(&pq->ev);
                handle_query(pq);
        }
        return;
fail:
        free_pending_query(pq);
}

void
tcp_response(int fd, short events, void *arg)
{
        struct pending_query    *pq;
        ssize_t                  n;

        pq = arg;

        n = write(fd, sldns_buffer_current(pq->abuf),
            sldns_buffer_remaining(pq->abuf));

        if (n == -1) {
                if (errno == EAGAIN || errno == EINTR)
                        return;
                free_pending_query(pq);
                return;
        }
        sldns_buffer_skip(pq->abuf, n);
        if (sldns_buffer_remaining(pq->abuf) == 0)
                free_pending_query(pq);
}

void
tcp_timeout(int fd, short events, void *arg)
{
        free_pending_query(arg);
}

void
check_available_af(void)
{
        static int               available_af = HAVE_IPV4 | HAVE_IPV6;
        static int               rtable = -1;
        struct ifaddrs          *ifap, *ifa;
        struct if_data          *ifa_data;
        struct sockaddr_in      *sin4;
        struct sockaddr_in6     *sin6;
        int                      new_available_af = 0, ifa_rtable = -1;

        if (rtable == -1)
                rtable = getrtable();

        if (getifaddrs(&ifap) != 0) {
                log_warn("getifaddrs");
                return;
        }

        for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
                if (ifa->ifa_addr == NULL)
                        continue;
                switch(ifa->ifa_addr->sa_family) {
                case AF_LINK:
                        /* AF_LINK comes before inet / inet6 on an interface */
                        ifa_data = (struct if_data *)ifa->ifa_data;
                        ifa_rtable = ifa_data->ifi_rdomain;
                        break;
                case AF_INET:
                        if (ifa_rtable != rtable)
                                continue;

                        sin4 = (struct sockaddr_in *)ifa->ifa_addr;
                        if ((ntohl(sin4->sin_addr.s_addr) >> 24) ==
                            IN_LOOPBACKNET)
                                continue;
                        new_available_af |= HAVE_IPV4;
                        break;
                case AF_INET6:
                        if (ifa_rtable != rtable)
                                continue;

                        sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
                        if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) ||
                            IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
                            IN6_IS_ADDR_MC_LINKLOCAL(&sin6->sin6_addr) ||
                            IN6_IS_ADDR_MC_INTFACELOCAL(&sin6->sin6_addr))
                                continue;
                        new_available_af |= HAVE_IPV6;
                        break;
                default:
                        break;
                }
                if (new_available_af == (HAVE_IPV4 | HAVE_IPV6))
                        break;
        }
        freeifaddrs(ifap);
        if (new_available_af != available_af) {
                available_af = new_available_af;
                frontend_imsg_compose_resolver(IMSG_CHANGE_AFS, 0,
                    &available_af, sizeof(available_af));
        }
}

void
reverse(char *begin, char *end)
{
        char    t;

        while (begin < --end) {
                t = *begin;
                *begin = *end;
                *end = t;
                ++begin;
        }
}