/*      $NetBSD: ntp_intres.c,v 1.13 2024/08/18 20:47:13 christos Exp $ */

/*
* ntp_intres.c - Implements a generic blocking worker child or thread,
*                initially to provide a nonblocking solution for DNS
*                name to address lookups available with getaddrinfo().
*
* This is a new implementation as of 2009 sharing the filename and
* very little else with the prior implementation, which used a
* temporary file to receive a single set of requests from the parent,
* and a NTP mode 7 authenticated request to push back responses.
*
* A primary goal in rewriting this code was the need to support the
* pool configuration directive's requirement to retrieve multiple
* addresses resolving a single name, which has previously been
* satisfied with blocking resolver calls from the ntpd mainline code.
*
* A secondary goal is to provide a generic mechanism for other
* blocking operations to be delegated to a worker using a common
* model for both Unix and Windows ntpd.  ntp_worker.c, work_fork.c,
* and work_thread.c implement the generic mechanism.  This file
* implements the two current consumers, getaddrinfo_sometime() and the
* presently unused getnameinfo_sometime().
*
* Both routines deliver results to a callback and manage memory
* allocation, meaning there is no freeaddrinfo_sometime().
*
* The initial implementation for Unix uses a pair of unidirectional
* pipes, one each for requests and responses, connecting the forked
* blocking child worker with the ntpd mainline.  The threaded code
* uses arrays of pointers to queue requests and responses.
*
* The parent drives the process, including scheduling sleeps between
* retries.
*
* Memory is managed differently for a child process, which mallocs
* request buffers to read from the pipe into, whereas the threaded
* code mallocs a copy of the request to hand off to the worker via
* the queueing array.  The resulting request buffer is free()d by
* platform-independent code.  A wrinkle is the request needs to be
* available to the requestor during response processing.
*
* Response memory allocation is also platform-dependent.  With a
* separate process and pipes, the response is free()d after being
* written to the pipe.  With threads, the same memory is handed
* over and the requestor frees it after processing is completed.
*
* The code should be generalized to support threads on Unix using
* much of the same code used for Windows initially.
*
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "ntp_workimpl.h"

#ifdef WORKER

#include <stdio.h>
#include <ctype.h>
#include <signal.h>

/**/
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <arpa/inet.h>
/**/
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif

#if !defined(HAVE_RES_INIT) && defined(HAVE___RES_INIT)
# define HAVE_RES_INIT
#endif

#if defined(HAVE_RESOLV_H) && defined(HAVE_RES_INIT)
# ifdef HAVE_ARPA_NAMESER_H
#  include <arpa/nameser.h> /* DNS HEADER struct */
# endif
# ifdef HAVE_NETDB_H
#  include <netdb.h>
# endif
# include <resolv.h>
#endif

#include "ntp.h"
#include "ntp_debug.h"
#include "ntp_malloc.h"
#include "ntp_syslog.h"
#include "ntp_unixtime.h"
#include "ntp_intres.h"
#include "intreswork.h"


/*
* Following are implementations of getaddrinfo_sometime() and
* getnameinfo_sometime().  Each is implemented in three routines:
*
* getaddrinfo_sometime()               getnameinfo_sometime()
* blocking_getaddrinfo()               blocking_getnameinfo()
* getaddrinfo_sometime_complete()      getnameinfo_sometime_complete()
*
* The first runs in the parent and marshalls (or serializes) request
* parameters into a request blob which is processed in the child by
* the second routine, blocking_*(), which serializes the results into
* a response blob unpacked by the third routine, *_complete(), which
* calls the callback routine provided with the request and frees
* _request_ memory allocated by the first routine.  Response memory
* is managed by the code which calls the *_complete routines.
*/


/* === typedefs === */
typedef struct blocking_gai_req_tag {   /* marshalled args */
       size_t                  octets;
       u_int                   dns_idx;
       time_t                  scheduled;
       time_t                  earliest;
       int                     retry;
       struct addrinfo         hints;
       u_int                   qflags;
       gai_sometime_callback   callback;
       void *                  context;
       size_t                  nodesize;
       size_t                  servsize;
} blocking_gai_req;

typedef struct blocking_gai_resp_tag {
       size_t                  octets;
       int                     retcode;
       int                     retry;
       int                     gai_errno; /* for EAI_SYSTEM case */
       int                     ai_count;
       /*
        * Followed by ai_count struct addrinfo and then ai_count
        * sockaddr_u and finally the canonical name strings.
        */
} blocking_gai_resp;

typedef struct blocking_gni_req_tag {
       size_t                  octets;
       u_int                   dns_idx;
       time_t                  scheduled;
       time_t                  earliest;
       int                     retry;
       size_t                  hostoctets;
       size_t                  servoctets;
       int                     flags;
       gni_sometime_callback   callback;
       void *                  context;
       sockaddr_u              socku;
} blocking_gni_req;

typedef struct blocking_gni_resp_tag {
       size_t                  octets;
       int                     retcode;
       int                     gni_errno; /* for EAI_SYSTEM case */
       int                     retry;
       size_t                  hostoctets;
       size_t                  servoctets;
       /*
        * Followed by hostoctets bytes of null-terminated host,
        * then servoctets bytes of null-terminated service.
        */
} blocking_gni_resp;

/* per-DNS-worker state in parent */
typedef struct dnschild_ctx_tag {
       u_int   index;
       time_t  next_dns_timeslot;
} dnschild_ctx;

/* per-DNS-worker state in worker */
typedef struct dnsworker_ctx_tag {
       blocking_child *        c;
       time_t                  ignore_scheduled_before;
#ifdef HAVE_RES_INIT
       time_t  next_res_init;
#endif
} dnsworker_ctx;


/* === variables === */
dnschild_ctx **         dnschild_contexts;              /* parent */
u_int                   dnschild_contexts_alloc;
dnsworker_ctx **        dnsworker_contexts;             /* child */
u_int                   dnsworker_contexts_alloc;

#ifdef HAVE_RES_INIT
static  time_t          next_res_init;
#endif


/* === forward declarations === */
static  u_int           reserve_dnschild_ctx(void);
static  u_int           get_dnschild_ctx(void);
static  dnsworker_ctx * get_worker_context(blocking_child *, u_int);
static  void            scheduled_sleep(time_t, time_t,
                                       dnsworker_ctx *);
static  void            manage_dns_retry_interval(time_t *, time_t *,
                                                 int *, time_t *,
                                                 int/*BOOL*/);
static  int             should_retry_dns(int, int);
#ifdef HAVE_RES_INIT
static  void            reload_resolv_conf(dnsworker_ctx *);
#else
# define                reload_resolv_conf(wc)          \
       do {                                            \
               (void)(wc);                             \
       } while (FALSE)
#endif
static  void            getaddrinfo_sometime_complete(blocking_work_req,
                                                     void *, size_t,
                                                     void *);
static  void            getnameinfo_sometime_complete(blocking_work_req,
                                                     void *, size_t,
                                                     void *);


/* === functions === */
/*
* getaddrinfo_sometime - uses blocking child to call getaddrinfo then
*                        invokes provided callback completion function.
*/
int
getaddrinfo_sometime_ex(
       const char *            node,
       const char *            service,
       const struct addrinfo * hints,
       int                     retry,
       gai_sometime_callback   callback,
       void *                  context,
       u_int                   qflags
       )
{
       blocking_gai_req *      gai_req;
       u_int                   idx;
       dnschild_ctx *          child_ctx;
       size_t                  req_size;
       size_t                  nodesize;
       size_t                  servsize;
       time_t                  now;

       REQUIRE(NULL != node);
       if (NULL != hints) {
               REQUIRE(0 == hints->ai_addrlen);
               REQUIRE(NULL == hints->ai_addr);
               REQUIRE(NULL == hints->ai_canonname);
               REQUIRE(NULL == hints->ai_next);
       }

       idx = get_dnschild_ctx();
       child_ctx = dnschild_contexts[idx];

       nodesize = strlen(node) + 1;
       servsize = strlen(service) + 1;
       req_size = sizeof(*gai_req) + nodesize + servsize;

       gai_req = emalloc_zero(req_size);

       gai_req->octets = req_size;
       gai_req->dns_idx = idx;
       now = time(NULL);
       gai_req->scheduled = now;
       gai_req->earliest = max(now, child_ctx->next_dns_timeslot);
       child_ctx->next_dns_timeslot = gai_req->earliest;
       if (hints != NULL)
               gai_req->hints = *hints;
       gai_req->retry = retry;
       gai_req->callback = callback;
       gai_req->context = context;
       gai_req->nodesize = nodesize;
       gai_req->servsize = servsize;
       gai_req->qflags = qflags;

       memcpy((char *)gai_req + sizeof(*gai_req), node, nodesize);
       memcpy((char *)gai_req + sizeof(*gai_req) + nodesize, service,
              servsize);

       if (queue_blocking_request(
               BLOCKING_GETADDRINFO,
               gai_req,
               req_size,
               &getaddrinfo_sometime_complete,
               gai_req)) {

               msyslog(LOG_ERR, "unable to queue getaddrinfo request");
               errno = EFAULT;
               return -1;
       }

       return 0;
}

int
blocking_getaddrinfo(
       blocking_child *        c,
       blocking_pipe_header *  req
       )
{
       blocking_gai_req *      gai_req;
       dnsworker_ctx *         worker_ctx;
       blocking_pipe_header *  resp;
       blocking_gai_resp *     gai_resp;
       char *                  node;
       char *                  service;
       struct addrinfo *       ai_res;
       struct addrinfo *       ai;
       struct addrinfo *       serialized_ai;
       size_t                  canons_octets;
       size_t                  this_octets;
       size_t                  resp_octets;
       char *                  cp;
       time_t                  time_now;

       gai_req = (void *)((char *)req + sizeof(*req));
       node = (char *)gai_req + sizeof(*gai_req);
       service = node + gai_req->nodesize;

       worker_ctx = get_worker_context(c, gai_req->dns_idx);
       scheduled_sleep(gai_req->scheduled, gai_req->earliest,
                       worker_ctx);
       reload_resolv_conf(worker_ctx);

       /*
        * Take a shot at the final size, better to overestimate
        * at first and then realloc to a smaller size.
        */

       resp_octets = sizeof(*resp) + sizeof(*gai_resp) +
                     16 * (sizeof(struct addrinfo) +
                           sizeof(sockaddr_u)) +
                     256;
       resp = emalloc_zero(resp_octets);
       gai_resp = (void *)(resp + 1);

       TRACE(2, ("blocking_getaddrinfo given node %s serv %s fam %d flags %x\n",
                 node, service, gai_req->hints.ai_family,
                 gai_req->hints.ai_flags));
#ifdef DEBUG
       if (debug >= 2)
               fflush(stdout);
#endif
       ai_res = NULL;
       gai_resp->retcode = getaddrinfo(node, service, &gai_req->hints,
                                       &ai_res);
       gai_resp->retry = gai_req->retry;
#ifdef EAI_SYSTEM
       if (EAI_SYSTEM == gai_resp->retcode)
               gai_resp->gai_errno = errno;
#endif
       canons_octets = 0;

       if (0 == gai_resp->retcode) {
               ai = ai_res;
               while (NULL != ai) {
                       gai_resp->ai_count++;
                       if (ai->ai_canonname)
                               canons_octets += strlen(ai->ai_canonname) + 1;
                       ai = ai->ai_next;
               }
               /*
                * If this query succeeded only after retrying, DNS may have
                * just become responsive.  Ignore previously-scheduled
                * retry sleeps once for each pending request, similar to
                * the way scheduled_sleep() does when its worker_sleep()
                * is interrupted.
                */
               if (gai_resp->retry > INITIAL_DNS_RETRY) {
                       time_now = time(NULL);
                       worker_ctx->ignore_scheduled_before = time_now;
                       TRACE(1, ("DNS success after retry, ignoring sleeps scheduled before now (%s)\n",
                                 humantime(time_now)));
               }
       }

       /*
        * Our response consists of a header, followed by ai_count
        * addrinfo structs followed by ai_count sockaddr_storage
        * structs followed by the canonical names.
        */
       gai_resp->octets = sizeof(*gai_resp)
                           + gai_resp->ai_count
                               * (sizeof(gai_req->hints)
                                  + sizeof(sockaddr_u))
                           + canons_octets;

       resp_octets = sizeof(*resp) + gai_resp->octets;
       resp = erealloc(resp, resp_octets);
       gai_resp = (void *)(resp + 1);

       /* cp serves as our current pointer while serializing */
       cp = (void *)(gai_resp + 1);
       canons_octets = 0;

       if (0 == gai_resp->retcode) {
               ai = ai_res;
               while (NULL != ai) {
                       memcpy(cp, ai, sizeof(*ai));
                       serialized_ai = (void *)cp;
                       cp += sizeof(*ai);

                       /* transform ai_canonname into offset */
                       if (NULL != ai->ai_canonname) {
                               serialized_ai->ai_canonname = (char *)canons_octets;
                               canons_octets += strlen(ai->ai_canonname) + 1;
                       }

                       /* leave fixup of ai_addr pointer for receiver */

                       ai = ai->ai_next;
               }

               ai = ai_res;
               while (NULL != ai) {
                       INSIST(ai->ai_addrlen <= sizeof(sockaddr_u));
                       memcpy(cp, ai->ai_addr, ai->ai_addrlen);
                       cp += sizeof(sockaddr_u);

                       ai = ai->ai_next;
               }

               ai = ai_res;
               while (NULL != ai) {
                       if (NULL != ai->ai_canonname) {
                               this_octets = strlen(ai->ai_canonname) + 1;
                               memcpy(cp, ai->ai_canonname, this_octets);
                               cp += this_octets;
                       }

                       ai = ai->ai_next;
               }
               freeaddrinfo(ai_res);
       }

       /*
        * make sure our walk and earlier calc match
        */
       DEBUG_INSIST((size_t)(cp - (char *)resp) == resp_octets);

       if (queue_blocking_response(c, resp, resp_octets, req)) {
               msyslog(LOG_ERR, "blocking_getaddrinfo can not queue response");
               return -1;
       }

       return 0;
}

int
getaddrinfo_sometime(
       const char *            node,
       const char *            service,
       const struct addrinfo * hints,
       int                     retry,
       gai_sometime_callback   callback,
       void *                  context
       )
{
       return getaddrinfo_sometime_ex(node, service, hints, retry,
                                      callback, context, 0);
}


static void
getaddrinfo_sometime_complete(
       blocking_work_req       rtype,
       void *                  context,
       size_t                  respsize,
       void *                  resp
       )
{
       blocking_gai_req *      gai_req;
       blocking_gai_resp *     gai_resp;
       dnschild_ctx *          child_ctx;
       struct addrinfo *       ai;
       struct addrinfo *       next_ai;
       sockaddr_u *            psau;
       char *                  node;
       char *                  service;
       char *                  canon_start;
       time_t                  time_now;
       int                     again, noerr;
       int                     af;
       const char *            fam_spec;
       int                     i;

       gai_req = context;
       gai_resp = resp;

       DEBUG_REQUIRE(BLOCKING_GETADDRINFO == rtype);
       DEBUG_REQUIRE(respsize == gai_resp->octets);

       node = (char *)gai_req + sizeof(*gai_req);
       service = node + gai_req->nodesize;

       child_ctx = dnschild_contexts[gai_req->dns_idx];

       if (0 == gai_resp->retcode) {
               /*
                * If this query succeeded only after retrying, DNS may have
                * just become responsive.
                */
               if (gai_resp->retry > INITIAL_DNS_RETRY) {
                       time_now = time(NULL);
                       child_ctx->next_dns_timeslot = time_now;
                       TRACE(1, ("DNS success after retry, %u next_dns_timeslot reset (%s)\n",
                                 gai_req->dns_idx, humantime(time_now)));
               }
       } else {
               noerr = !!(gai_req->qflags & GAIR_F_IGNDNSERR);
               again = noerr || should_retry_dns(
                                       gai_resp->retcode, gai_resp->gai_errno);
               /*
                * exponential backoff of DNS retries to 64s
                */
               if (gai_req->retry > 0 && again) {
                       /* log the first retry only */
                       if (INITIAL_DNS_RETRY == gai_req->retry)
                               NLOG(NLOG_SYSINFO) {
                                       af = gai_req->hints.ai_family;
                                       fam_spec = (AF_INET6 == af)
                                                      ? " (AAAA)"
                                                      : (AF_INET == af)
                                                            ? " (A)"
                                                            : "";
#ifdef EAI_SYSTEM
                                       if (EAI_SYSTEM == gai_resp->retcode) {
                                               errno = gai_resp->gai_errno;
                                               msyslog(LOG_INFO,
                                                       "retrying DNS %s%s: EAI_SYSTEM %d: %m",
                                                       node, fam_spec,
                                                       gai_resp->gai_errno);
                                       } else
#endif
                                               msyslog(LOG_INFO,
                                                       "retrying DNS %s%s: %s (%d)",
                                                       node, fam_spec,
                                                       gai_strerror(gai_resp->retcode),
                                                       gai_resp->retcode);
                               }
                       manage_dns_retry_interval(
                               &gai_req->scheduled, &gai_req->earliest,
                               &gai_req->retry, &child_ctx->next_dns_timeslot,
                               noerr);
                       if (!queue_blocking_request(
                                       BLOCKING_GETADDRINFO,
                                       gai_req,
                                       gai_req->octets,
                                       &getaddrinfo_sometime_complete,
                                       gai_req))
                               return;
                       else
                               msyslog(LOG_ERR,
                                       "unable to retry hostname %s",
                                       node);
               }
       }

       /*
        * fixup pointers in returned addrinfo array
        */
       ai = (void *)((char *)gai_resp + sizeof(*gai_resp));
       next_ai = NULL;
       for (i = gai_resp->ai_count - 1; i >= 0; i--) {
               ai[i].ai_next = next_ai;
               next_ai = &ai[i];
       }

       psau = (void *)((char *)ai + gai_resp->ai_count * sizeof(*ai));
       canon_start = (char *)psau + gai_resp->ai_count * sizeof(*psau);

       for (i = 0; i < gai_resp->ai_count; i++) {
               if (NULL != ai[i].ai_addr)
                       ai[i].ai_addr = &psau->sa;
               psau++;
               if (NULL != ai[i].ai_canonname)
                       ai[i].ai_canonname += (size_t)canon_start;
       }

       ENSURE((char *)psau == canon_start);

       if (!gai_resp->ai_count)
               ai = NULL;

       (*gai_req->callback)(gai_resp->retcode, gai_resp->gai_errno,
                            gai_req->context, node, service,
                            &gai_req->hints, ai);

       free(gai_req);
       /* gai_resp is part of block freed by process_blocking_resp() */
}


#ifdef TEST_BLOCKING_WORKER
void gai_test_callback(int rescode, int gai_errno, void *context, const char *name, const char *service, const struct addrinfo *hints, const struct addrinfo *ai_res)
{
       sockaddr_u addr;

       if (rescode) {
               TRACE(1, ("gai_test_callback context %p error rescode %d %s serv %s\n",
                         context, rescode, name, service));
               return;
       }
       while (!rescode && NULL != ai_res) {
               ZERO_SOCK(&addr);
               memcpy(&addr, ai_res->ai_addr, ai_res->ai_addrlen);
               TRACE(1, ("ctx %p fam %d addr %s canon '%s' type %s at %p ai_addr %p ai_next %p\n",
                         context,
                         AF(&addr),
                         stoa(&addr),
                         (ai_res->ai_canonname)
                             ? ai_res->ai_canonname
                             : "",
                         (SOCK_DGRAM == ai_res->ai_socktype)
                             ? "DGRAM"
                             : (SOCK_STREAM == ai_res->ai_socktype)
                                   ? "STREAM"
                                   : "(other)",
                         ai_res,
                         ai_res->ai_addr,
                         ai_res->ai_next));

               getnameinfo_sometime((sockaddr_u *)ai_res->ai_addr, 128, 32, 0, gni_test_callback, context);

               ai_res = ai_res->ai_next;
       }
}
#endif  /* TEST_BLOCKING_WORKER */


int
getnameinfo_sometime(
       sockaddr_u *            psau,
       size_t                  hostoctets,
       size_t                  servoctets,
       int                     flags,
       gni_sometime_callback   callback,
       void *                  context
       )
{
       blocking_gni_req *      gni_req;
       u_int                   idx;
       dnschild_ctx *          child_ctx;
       time_t                  time_now;

       REQUIRE(hostoctets);
       REQUIRE(hostoctets + servoctets < 1024);

       idx = get_dnschild_ctx();
       child_ctx = dnschild_contexts[idx];

       gni_req = emalloc_zero(sizeof(*gni_req));

       gni_req->octets = sizeof(*gni_req);
       gni_req->dns_idx = idx;
       time_now = time(NULL);
       gni_req->scheduled = time_now;
       gni_req->earliest = max(time_now, child_ctx->next_dns_timeslot);
       child_ctx->next_dns_timeslot = gni_req->earliest;
       memcpy(&gni_req->socku, psau, SOCKLEN(psau));
       gni_req->hostoctets = hostoctets;
       gni_req->servoctets = servoctets;
       gni_req->flags = flags;
       gni_req->retry = INITIAL_DNS_RETRY;
       gni_req->callback = callback;
       gni_req->context = context;

       if (queue_blocking_request(
               BLOCKING_GETNAMEINFO,
               gni_req,
               sizeof(*gni_req),
               &getnameinfo_sometime_complete,
               gni_req)) {

               msyslog(LOG_ERR, "unable to queue getnameinfo request");
               errno = EFAULT;
               return -1;
       }

       return 0;
}


int
blocking_getnameinfo(
       blocking_child *        c,
       blocking_pipe_header *  req
       )
{
       blocking_gni_req *      gni_req;
       dnsworker_ctx *         worker_ctx;
       blocking_pipe_header *  resp;
       blocking_gni_resp *     gni_resp;
       size_t                  octets;
       size_t                  resp_octets;
       char *                  service;
       char *                  cp;
       int                     rc;
       time_t                  time_now;
       char                    host[1024];

       gni_req = (void *)((char *)req + sizeof(*req));

       octets = gni_req->hostoctets + gni_req->servoctets;

       /*
        * Some alloca() implementations are fragile regarding
        * large allocations.  We only need room for the host
        * and service names.
        */
       REQUIRE(octets < sizeof(host));
       service = host + gni_req->hostoctets;

       worker_ctx = get_worker_context(c, gni_req->dns_idx);
       scheduled_sleep(gni_req->scheduled, gni_req->earliest,
                       worker_ctx);
       reload_resolv_conf(worker_ctx);

       /*
        * Take a shot at the final size, better to overestimate
        * then realloc to a smaller size.
        */

       resp_octets = sizeof(*resp) + sizeof(*gni_resp) + octets;
       resp = emalloc_zero(resp_octets);
       gni_resp = (void *)((char *)resp + sizeof(*resp));

       TRACE(2, ("blocking_getnameinfo given addr %s flags 0x%x hostlen %lu servlen %lu\n",
                 stoa(&gni_req->socku), gni_req->flags,
                 (u_long)gni_req->hostoctets, (u_long)gni_req->servoctets));

       gni_resp->retcode = getnameinfo(&gni_req->socku.sa,
                                       SOCKLEN(&gni_req->socku),
                                       host,
                                       gni_req->hostoctets,
                                       service,
                                       gni_req->servoctets,
                                       gni_req->flags);
       gni_resp->retry = gni_req->retry;
#ifdef EAI_SYSTEM
       if (EAI_SYSTEM == gni_resp->retcode)
               gni_resp->gni_errno = errno;
#endif

       if (0 != gni_resp->retcode) {
               gni_resp->hostoctets = 0;
               gni_resp->servoctets = 0;
       } else {
               gni_resp->hostoctets = strlen(host) + 1;
               gni_resp->servoctets = strlen(service) + 1;
               /*
                * If this query succeeded only after retrying, DNS may have
                * just become responsive.  Ignore previously-scheduled
                * retry sleeps once for each pending request, similar to
                * the way scheduled_sleep() does when its worker_sleep()
                * is interrupted.
                */
               if (gni_req->retry > INITIAL_DNS_RETRY) {
                       time_now = time(NULL);
                       worker_ctx->ignore_scheduled_before = time_now;
                       TRACE(1, ("DNS success after retrying, ignoring sleeps scheduled before now (%s)\n",
                               humantime(time_now)));
               }
       }
       octets = gni_resp->hostoctets + gni_resp->servoctets;
       /*
        * Our response consists of a header, followed by the host and
        * service strings, each null-terminated.
        */
       resp_octets = sizeof(*resp) + sizeof(*gni_resp) + octets;

       resp = erealloc(resp, resp_octets);
       gni_resp = (void *)(resp + 1);

       gni_resp->octets = sizeof(*gni_resp) + octets;

       /* cp serves as our current pointer while serializing */
       cp = (void *)(gni_resp + 1);

       if (0 == gni_resp->retcode) {
               memcpy(cp, host, gni_resp->hostoctets);
               cp += gni_resp->hostoctets;
               memcpy(cp, service, gni_resp->servoctets);
               cp += gni_resp->servoctets;
       }

       INSIST((size_t)(cp - (char *)resp) == resp_octets);
       INSIST(resp_octets - sizeof(*resp) == gni_resp->octets);

       rc = queue_blocking_response(c, resp, resp_octets, req);
       if (rc)
               msyslog(LOG_ERR, "blocking_getnameinfo unable to queue response");
       return rc;
}


static void
getnameinfo_sometime_complete(
       blocking_work_req       rtype,
       void *                  context,
       size_t                  respsize,
       void *                  resp
       )
{
       blocking_gni_req *      gni_req;
       blocking_gni_resp *     gni_resp;
       dnschild_ctx *          child_ctx;
       char *                  host;
       char *                  service;
       time_t                  time_now;
       int                     again;

       gni_req = context;
       gni_resp = resp;

       DEBUG_REQUIRE(BLOCKING_GETNAMEINFO == rtype);
       DEBUG_REQUIRE(respsize == gni_resp->octets);

       child_ctx = dnschild_contexts[gni_req->dns_idx];

       if (0 == gni_resp->retcode) {
               /*
                * If this query succeeded only after retrying, DNS may have
                * just become responsive.
                */
               if (gni_resp->retry > INITIAL_DNS_RETRY) {
                       time_now = time(NULL);
                       child_ctx->next_dns_timeslot = time_now;
                       TRACE(1, ("DNS success after retry, %u next_dns_timeslot reset (%s)\n",
                                 gni_req->dns_idx, humantime(time_now)));
               }
       } else {
               again = should_retry_dns(gni_resp->retcode, gni_resp->gni_errno);
               /*
                * exponential backoff of DNS retries to 64s
                */
               if (gni_req->retry > 0)
                       manage_dns_retry_interval(&gni_req->scheduled,
                           &gni_req->earliest, &gni_req->retry,
                                                 &child_ctx->next_dns_timeslot, FALSE);

               if (gni_req->retry > 0 && again) {
                       if (!queue_blocking_request(
                               BLOCKING_GETNAMEINFO,
                               gni_req,
                               gni_req->octets,
                               &getnameinfo_sometime_complete,
                               gni_req))
                               return;

                       msyslog(LOG_ERR, "unable to retry reverse lookup of %s", stoa(&gni_req->socku));
               }
       }

       if (!gni_resp->hostoctets) {
               host = NULL;
               service = NULL;
       } else {
               host = (char *)gni_resp + sizeof(*gni_resp);
               service = (gni_resp->servoctets)
                             ? host + gni_resp->hostoctets
                             : NULL;
       }

       (*gni_req->callback)(gni_resp->retcode, gni_resp->gni_errno,
                            &gni_req->socku, gni_req->flags, host,
                            service, gni_req->context);

       free(gni_req);
       /* gni_resp is part of block freed by process_blocking_resp() */
}


#ifdef TEST_BLOCKING_WORKER
void gni_test_callback(int rescode, int gni_errno, sockaddr_u *psau, int flags, const char *host, const char *service, void *context)
{
       if (!rescode)
               TRACE(1, ("gni_test_callback got host '%s' serv '%s' for addr %s context %p\n",
                         host, service, stoa(psau), context));
       else
               TRACE(1, ("gni_test_callback context %p rescode %d gni_errno %d flags 0x%x addr %s\n",
                         context, rescode, gni_errno, flags, stoa(psau)));
}
#endif  /* TEST_BLOCKING_WORKER */


#ifdef HAVE_RES_INIT
static void
reload_resolv_conf(
       dnsworker_ctx * worker_ctx
       )
{
       time_t  time_now;

       /*
        * This is ad-hoc.  Reload /etc/resolv.conf once per minute
        * to pick up on changes from the DHCP client.  [Bug 1226]
        * When using threads for the workers, this needs to happen
        * only once per minute process-wide.
        */
       time_now = time(NULL);
# ifdef WORK_THREAD
       worker_ctx->next_res_init = next_res_init;
# endif
       if (worker_ctx->next_res_init <= time_now) {
               if (worker_ctx->next_res_init != 0)
                       res_init();
               worker_ctx->next_res_init = time_now + 60;
# ifdef WORK_THREAD
               next_res_init = worker_ctx->next_res_init;
# endif
       }
}
#endif  /* HAVE_RES_INIT */


static u_int
reserve_dnschild_ctx(void)
{
       const size_t    ps = sizeof(dnschild_contexts[0]);
       const size_t    cs = sizeof(*dnschild_contexts[0]);
       u_int           c;
       u_int           new_alloc;
       size_t          octets;
       size_t          new_octets;

       c = 0;
       while (TRUE) {
               for ( ; c < dnschild_contexts_alloc; c++) {
                       if (NULL == dnschild_contexts[c]) {
                               dnschild_contexts[c] = emalloc_zero(cs);

                               return c;
                       }
               }
               new_alloc = dnschild_contexts_alloc + 20;
               new_octets = new_alloc * ps;
               octets = dnschild_contexts_alloc * ps;
               dnschild_contexts = erealloc_zero(dnschild_contexts,
                                                 new_octets, octets);
               dnschild_contexts_alloc = new_alloc;
       }
}


static u_int
get_dnschild_ctx(void)
{
       static u_int    shared_ctx = UINT_MAX;

       if (worker_per_query)
               return reserve_dnschild_ctx();

       if (UINT_MAX == shared_ctx)
               shared_ctx = reserve_dnschild_ctx();

       return shared_ctx;
}


static dnsworker_ctx *
get_worker_context(
       blocking_child *        c,
       u_int                   idx
       )
{
       u_int           min_new_alloc;
       u_int           new_alloc;
       size_t          octets;
       size_t          new_octets;
       dnsworker_ctx * retv;

       worker_global_lock(TRUE);

       if (dnsworker_contexts_alloc <= idx) {
               min_new_alloc = 1 + idx;
               /* round new_alloc up to nearest multiple of 4 */
               new_alloc = (min_new_alloc + 4) & ~(4 - 1);
               new_octets = new_alloc * sizeof(dnsworker_ctx*);
               octets = dnsworker_contexts_alloc * sizeof(dnsworker_ctx*);
               dnsworker_contexts = erealloc_zero(dnsworker_contexts,
                                                  new_octets, octets);
               dnsworker_contexts_alloc = new_alloc;
               retv = emalloc_zero(sizeof(dnsworker_ctx));
               dnsworker_contexts[idx] = retv;
       } else if (NULL == (retv = dnsworker_contexts[idx])) {
               retv = emalloc_zero(sizeof(dnsworker_ctx));
               dnsworker_contexts[idx] = retv;
       }

       worker_global_lock(FALSE);

       ZERO(*retv);
       retv->c = c;
       return retv;
}


static void
scheduled_sleep(
       time_t          scheduled,
       time_t          earliest,
       dnsworker_ctx * worker_ctx
       )
{
       time_t now;

       if (scheduled < worker_ctx->ignore_scheduled_before) {
               TRACE(1, ("ignoring sleep until %s scheduled at %s (before %s)\n",
                         humantime(earliest), humantime(scheduled),
                         humantime(worker_ctx->ignore_scheduled_before)));
               return;
       }

       now = time(NULL);

       if (now < earliest) {
               TRACE(1, ("sleep until %s scheduled at %s (>= %s)\n",
                         humantime(earliest), humantime(scheduled),
                         humantime(worker_ctx->ignore_scheduled_before)));
               if (-1 == worker_sleep(worker_ctx->c, earliest - now)) {
                       /* our sleep was interrupted */
                       now = time(NULL);
                       worker_ctx->ignore_scheduled_before = now;
#ifdef HAVE_RES_INIT
                       worker_ctx->next_res_init = now + 60;
                       next_res_init = worker_ctx->next_res_init;
                       res_init();
#endif
                       TRACE(1, ("sleep interrupted by daemon, ignoring sleeps scheduled before now (%s)\n",
                                 humantime(worker_ctx->ignore_scheduled_before)));
               }
       }
}


/*
* manage_dns_retry_interval is a helper used by
* getaddrinfo_sometime_complete and getnameinfo_sometime_complete
* to calculate the new retry interval and schedule the next query.
*/
static void
manage_dns_retry_interval(
       time_t *        pscheduled,
       time_t *        pwhen,
       int *           pretry,
       time_t *        pnext_timeslot,
       int             forever
       )
{
       time_t  now;
       time_t  when;
       int     retry;
       int     retmax;

       now = time(NULL);
       retry = *pretry;
       when = max(now + retry, *pnext_timeslot);
       *pnext_timeslot = when;

       /* this exponential backoff is slower than doubling up: The
        * sequence goes 2-3-4-6-8-12-16-24-32... and the upper limit is
        * 64 seconds for things that should not repeat forever, and
        * 1024 when repeated forever.
        */
       retmax = forever ? 1024 : 64;
       retry <<= 1;
       if (retry & (retry - 1))
               retry &= (retry - 1);
       else
               retry -= (retry >> 2);
       retry = min(retmax, retry);

       *pscheduled = now;
       *pwhen = when;
       *pretry = retry;
}

/*
* should_retry_dns is a helper used by getaddrinfo_sometime_complete
* and getnameinfo_sometime_complete which implements ntpd's DNS retry
* policy.
*/
static int
should_retry_dns(
       int     rescode,
       int     res_errno
       )
{
       static int      eai_again_seen;
       int             again;
#if defined (EAI_SYSTEM) && defined(DEBUG)
       char            msg[256];
#endif

       /*
        * If the resolver failed, see if the failure is
        * temporary. If so, return success.
        */
       again = 0;

       switch (rescode) {

       case EAI_FAIL:
               again = 1;
               break;

       case EAI_AGAIN:
               again = 1;
               eai_again_seen = 1;             /* [Bug 1178] */
               break;

       case EAI_NONAME:
#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
       case EAI_NODATA:
#endif
               again = !eai_again_seen;        /* [Bug 1178] */
               break;

#ifdef EAI_SYSTEM
       case EAI_SYSTEM:
               /*
                * EAI_SYSTEM means the real error is in errno.  We should be more
                * discriminating about which errno values require retrying, but
                * this matches existing behavior.
                */
               again = 1;
# ifdef DEBUG
               errno_to_str(res_errno, msg, sizeof(msg));
               TRACE(1, ("intres: EAI_SYSTEM errno %d (%s) means try again, right?\n",
                         res_errno, msg));
# endif
               break;
#endif
       }

       TRACE(2, ("intres: resolver returned: %s (%d), %sretrying\n",
                 gai_strerror(rescode), rescode, again ? "" : "not "));

       return again;
}

#else   /* !WORKER follows */
int ntp_intres_nonempty_compilation_unit;
#endif