1155 lines
29 KiB
C
1155 lines
29 KiB
C
/*
|
|
* 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>
|
|
# ifdef HAVE_INT32_ONLY_WITH_DNS
|
|
# define HAVE_INT32
|
|
# endif
|
|
# ifdef HAVE_U_INT32_ONLY_WITH_DNS
|
|
# define HAVE_U_INT32
|
|
# endif
|
|
#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
|