1275970Scy/*
2275970Scy * ntp_intres.c - Implements a generic blocking worker child or thread,
3275970Scy *		  initially to provide a nonblocking solution for DNS
4275970Scy *		  name to address lookups available with getaddrinfo().
5275970Scy *
6275970Scy * This is a new implementation as of 2009 sharing the filename and
7275970Scy * very little else with the prior implementation, which used a
8275970Scy * temporary file to receive a single set of requests from the parent,
9275970Scy * and a NTP mode 7 authenticated request to push back responses.
10275970Scy *
11275970Scy * A primary goal in rewriting this code was the need to support the
12275970Scy * pool configuration directive's requirement to retrieve multiple
13275970Scy * addresses resolving a single name, which has previously been
14275970Scy * satisfied with blocking resolver calls from the ntpd mainline code.
15275970Scy *
16275970Scy * A secondary goal is to provide a generic mechanism for other
17275970Scy * blocking operations to be delegated to a worker using a common
18275970Scy * model for both Unix and Windows ntpd.  ntp_worker.c, work_fork.c,
19275970Scy * and work_thread.c implement the generic mechanism.  This file
20275970Scy * implements the two current consumers, getaddrinfo_sometime() and the
21275970Scy * presently unused getnameinfo_sometime().
22275970Scy *
23275970Scy * Both routines deliver results to a callback and manage memory
24275970Scy * allocation, meaning there is no freeaddrinfo_sometime().
25275970Scy *
26275970Scy * The initial implementation for Unix uses a pair of unidirectional
27275970Scy * pipes, one each for requests and responses, connecting the forked
28275970Scy * blocking child worker with the ntpd mainline.  The threaded code
29275970Scy * uses arrays of pointers to queue requests and responses.
30275970Scy *
31275970Scy * The parent drives the process, including scheduling sleeps between
32275970Scy * retries.
33275970Scy *
34275970Scy * Memory is managed differently for a child process, which mallocs
35275970Scy * request buffers to read from the pipe into, whereas the threaded
36275970Scy * code mallocs a copy of the request to hand off to the worker via
37275970Scy * the queueing array.  The resulting request buffer is free()d by
38275970Scy * platform-independent code.  A wrinkle is the request needs to be
39275970Scy * available to the requestor during response processing.
40275970Scy *
41275970Scy * Response memory allocation is also platform-dependent.  With a
42275970Scy * separate process and pipes, the response is free()d after being
43275970Scy * written to the pipe.  With threads, the same memory is handed
44275970Scy * over and the requestor frees it after processing is completed.
45275970Scy *
46275970Scy * The code should be generalized to support threads on Unix using
47275970Scy * much of the same code used for Windows initially.
48275970Scy *
49275970Scy */
50275970Scy#ifdef HAVE_CONFIG_H
51275970Scy# include <config.h>
52275970Scy#endif
53275970Scy
54275970Scy#include "ntp_workimpl.h"
55275970Scy
56275970Scy#ifdef WORKER
57275970Scy
58275970Scy#include <stdio.h>
59275970Scy#include <ctype.h>
60275970Scy#include <signal.h>
61275970Scy
62275970Scy/**/
63275970Scy#ifdef HAVE_SYS_TYPES_H
64275970Scy# include <sys/types.h>
65275970Scy#endif
66275970Scy#ifdef HAVE_NETINET_IN_H
67275970Scy#include <netinet/in.h>
68275970Scy#endif
69275970Scy#include <arpa/inet.h>
70275970Scy/**/
71275970Scy#ifdef HAVE_SYS_PARAM_H
72275970Scy# include <sys/param.h>
73275970Scy#endif
74275970Scy
75275970Scy#if !defined(HAVE_RES_INIT) && defined(HAVE___RES_INIT)
76275970Scy# define HAVE_RES_INIT
77275970Scy#endif
78275970Scy
79275970Scy#if defined(HAVE_RESOLV_H) && defined(HAVE_RES_INIT)
80275970Scy# ifdef HAVE_ARPA_NAMESER_H
81275970Scy#  include <arpa/nameser.h> /* DNS HEADER struct */
82275970Scy# endif
83275970Scy# ifdef HAVE_NETDB_H
84275970Scy#  include <netdb.h>
85275970Scy# endif
86275970Scy# include <resolv.h>
87275970Scy# ifdef HAVE_INT32_ONLY_WITH_DNS
88275970Scy#  define HAVE_INT32
89275970Scy# endif
90275970Scy# ifdef HAVE_U_INT32_ONLY_WITH_DNS
91275970Scy#  define HAVE_U_INT32
92275970Scy# endif
93275970Scy#endif
94275970Scy
95275970Scy#include "ntp.h"
96275970Scy#include "ntp_debug.h"
97275970Scy#include "ntp_malloc.h"
98275970Scy#include "ntp_syslog.h"
99275970Scy#include "ntp_unixtime.h"
100275970Scy#include "ntp_intres.h"
101275970Scy#include "intreswork.h"
102275970Scy
103275970Scy
104275970Scy/*
105275970Scy * Following are implementations of getaddrinfo_sometime() and
106275970Scy * getnameinfo_sometime().  Each is implemented in three routines:
107275970Scy *
108275970Scy * getaddrinfo_sometime()		getnameinfo_sometime()
109275970Scy * blocking_getaddrinfo()		blocking_getnameinfo()
110275970Scy * getaddrinfo_sometime_complete()	getnameinfo_sometime_complete()
111275970Scy *
112275970Scy * The first runs in the parent and marshalls (or serializes) request
113275970Scy * parameters into a request blob which is processed in the child by
114275970Scy * the second routine, blocking_*(), which serializes the results into
115275970Scy * a response blob unpacked by the third routine, *_complete(), which
116275970Scy * calls the callback routine provided with the request and frees
117275970Scy * _request_ memory allocated by the first routine.  Response memory
118275970Scy * is managed by the code which calls the *_complete routines.
119275970Scy */
120275970Scy
121310419Sdelphij
122275970Scy/* === typedefs === */
123275970Scytypedef struct blocking_gai_req_tag {	/* marshalled args */
124275970Scy	size_t			octets;
125275970Scy	u_int			dns_idx;
126275970Scy	time_t			scheduled;
127275970Scy	time_t			earliest;
128310419Sdelphij	int			retry;
129275970Scy	struct addrinfo		hints;
130310419Sdelphij	u_int			qflags;
131275970Scy	gai_sometime_callback	callback;
132275970Scy	void *			context;
133275970Scy	size_t			nodesize;
134275970Scy	size_t			servsize;
135275970Scy} blocking_gai_req;
136275970Scy
137275970Scytypedef struct blocking_gai_resp_tag {
138275970Scy	size_t			octets;
139275970Scy	int			retcode;
140275970Scy	int			retry;
141275970Scy	int			gai_errno; /* for EAI_SYSTEM case */
142275970Scy	int			ai_count;
143275970Scy	/*
144275970Scy	 * Followed by ai_count struct addrinfo and then ai_count
145275970Scy	 * sockaddr_u and finally the canonical name strings.
146275970Scy	 */
147275970Scy} blocking_gai_resp;
148275970Scy
149275970Scytypedef struct blocking_gni_req_tag {
150275970Scy	size_t			octets;
151275970Scy	u_int			dns_idx;
152275970Scy	time_t			scheduled;
153275970Scy	time_t			earliest;
154275970Scy	int			retry;
155275970Scy	size_t			hostoctets;
156275970Scy	size_t			servoctets;
157275970Scy	int			flags;
158275970Scy	gni_sometime_callback	callback;
159275970Scy	void *			context;
160275970Scy	sockaddr_u		socku;
161275970Scy} blocking_gni_req;
162275970Scy
163275970Scytypedef struct blocking_gni_resp_tag {
164275970Scy	size_t			octets;
165275970Scy	int			retcode;
166275970Scy	int			gni_errno; /* for EAI_SYSTEM case */
167275970Scy	int			retry;
168275970Scy	size_t			hostoctets;
169275970Scy	size_t			servoctets;
170275970Scy	/*
171275970Scy	 * Followed by hostoctets bytes of null-terminated host,
172275970Scy	 * then servoctets bytes of null-terminated service.
173275970Scy	 */
174275970Scy} blocking_gni_resp;
175275970Scy
176275970Scy/* per-DNS-worker state in parent */
177275970Scytypedef struct dnschild_ctx_tag {
178275970Scy	u_int	index;
179275970Scy	time_t	next_dns_timeslot;
180275970Scy} dnschild_ctx;
181275970Scy
182275970Scy/* per-DNS-worker state in worker */
183275970Scytypedef struct dnsworker_ctx_tag {
184275970Scy	blocking_child *	c;
185275970Scy	time_t			ignore_scheduled_before;
186275970Scy#ifdef HAVE_RES_INIT
187275970Scy	time_t	next_res_init;
188275970Scy#endif
189275970Scy} dnsworker_ctx;
190275970Scy
191275970Scy
192275970Scy/* === variables === */
193275970Scydnschild_ctx **		dnschild_contexts;		/* parent */
194275970Scyu_int			dnschild_contexts_alloc;
195275970Scydnsworker_ctx **	dnsworker_contexts;		/* child */
196275970Scyu_int			dnsworker_contexts_alloc;
197275970Scy
198275970Scy#ifdef HAVE_RES_INIT
199275970Scystatic	time_t		next_res_init;
200275970Scy#endif
201275970Scy
202275970Scy
203275970Scy/* === forward declarations === */
204275970Scystatic	u_int		reserve_dnschild_ctx(void);
205275970Scystatic	u_int		get_dnschild_ctx(void);
206275970Scystatic	dnsworker_ctx *	get_worker_context(blocking_child *, u_int);
207275970Scystatic	void		scheduled_sleep(time_t, time_t,
208275970Scy					dnsworker_ctx *);
209275970Scystatic	void		manage_dns_retry_interval(time_t *, time_t *,
210310419Sdelphij						  int *, time_t *,
211310419Sdelphij						  int/*BOOL*/);
212275970Scystatic	int		should_retry_dns(int, int);
213275970Scy#ifdef HAVE_RES_INIT
214275970Scystatic	void		reload_resolv_conf(dnsworker_ctx *);
215275970Scy#else
216275970Scy# define		reload_resolv_conf(wc)		\
217275970Scy	do {						\
218275970Scy		(void)(wc);				\
219275970Scy	} while (FALSE)
220275970Scy#endif
221275970Scystatic	void		getaddrinfo_sometime_complete(blocking_work_req,
222275970Scy						      void *, size_t,
223275970Scy						      void *);
224275970Scystatic	void		getnameinfo_sometime_complete(blocking_work_req,
225275970Scy						      void *, size_t,
226275970Scy						      void *);
227275970Scy
228275970Scy
229275970Scy/* === functions === */
230275970Scy/*
231275970Scy * getaddrinfo_sometime - uses blocking child to call getaddrinfo then
232275970Scy *			  invokes provided callback completion function.
233275970Scy */
234275970Scyint
235310419Sdelphijgetaddrinfo_sometime_ex(
236275970Scy	const char *		node,
237275970Scy	const char *		service,
238275970Scy	const struct addrinfo *	hints,
239275970Scy	int			retry,
240275970Scy	gai_sometime_callback	callback,
241310419Sdelphij	void *			context,
242310419Sdelphij	u_int			qflags
243275970Scy	)
244275970Scy{
245275970Scy	blocking_gai_req *	gai_req;
246275970Scy	u_int			idx;
247275970Scy	dnschild_ctx *		child_ctx;
248275970Scy	size_t			req_size;
249275970Scy	size_t			nodesize;
250275970Scy	size_t			servsize;
251275970Scy	time_t			now;
252275970Scy
253290000Sglebius	REQUIRE(NULL != node);
254275970Scy	if (NULL != hints) {
255290000Sglebius		REQUIRE(0 == hints->ai_addrlen);
256290000Sglebius		REQUIRE(NULL == hints->ai_addr);
257290000Sglebius		REQUIRE(NULL == hints->ai_canonname);
258290000Sglebius		REQUIRE(NULL == hints->ai_next);
259275970Scy	}
260275970Scy
261275970Scy	idx = get_dnschild_ctx();
262275970Scy	child_ctx = dnschild_contexts[idx];
263275970Scy
264275970Scy	nodesize = strlen(node) + 1;
265275970Scy	servsize = strlen(service) + 1;
266275970Scy	req_size = sizeof(*gai_req) + nodesize + servsize;
267275970Scy
268275970Scy	gai_req = emalloc_zero(req_size);
269275970Scy
270275970Scy	gai_req->octets = req_size;
271275970Scy	gai_req->dns_idx = idx;
272275970Scy	now = time(NULL);
273275970Scy	gai_req->scheduled = now;
274275970Scy	gai_req->earliest = max(now, child_ctx->next_dns_timeslot);
275275970Scy	child_ctx->next_dns_timeslot = gai_req->earliest;
276275970Scy	if (hints != NULL)
277275970Scy		gai_req->hints = *hints;
278275970Scy	gai_req->retry = retry;
279275970Scy	gai_req->callback = callback;
280275970Scy	gai_req->context = context;
281275970Scy	gai_req->nodesize = nodesize;
282275970Scy	gai_req->servsize = servsize;
283310419Sdelphij	gai_req->qflags = qflags;
284275970Scy
285275970Scy	memcpy((char *)gai_req + sizeof(*gai_req), node, nodesize);
286275970Scy	memcpy((char *)gai_req + sizeof(*gai_req) + nodesize, service,
287275970Scy	       servsize);
288275970Scy
289275970Scy	if (queue_blocking_request(
290275970Scy		BLOCKING_GETADDRINFO,
291275970Scy		gai_req,
292275970Scy		req_size,
293275970Scy		&getaddrinfo_sometime_complete,
294275970Scy		gai_req)) {
295275970Scy
296275970Scy		msyslog(LOG_ERR, "unable to queue getaddrinfo request");
297275970Scy		errno = EFAULT;
298275970Scy		return -1;
299275970Scy	}
300275970Scy
301275970Scy	return 0;
302275970Scy}
303275970Scy
304275970Scyint
305275970Scyblocking_getaddrinfo(
306275970Scy	blocking_child *	c,
307275970Scy	blocking_pipe_header *	req
308275970Scy	)
309275970Scy{
310275970Scy	blocking_gai_req *	gai_req;
311275970Scy	dnsworker_ctx *		worker_ctx;
312275970Scy	blocking_pipe_header *	resp;
313275970Scy	blocking_gai_resp *	gai_resp;
314275970Scy	char *			node;
315275970Scy	char *			service;
316275970Scy	struct addrinfo *	ai_res;
317275970Scy	struct addrinfo *	ai;
318275970Scy	struct addrinfo *	serialized_ai;
319275970Scy	size_t			canons_octets;
320275970Scy	size_t			this_octets;
321275970Scy	size_t			resp_octets;
322275970Scy	char *			cp;
323275970Scy	time_t			time_now;
324275970Scy
325275970Scy	gai_req = (void *)((char *)req + sizeof(*req));
326275970Scy	node = (char *)gai_req + sizeof(*gai_req);
327275970Scy	service = node + gai_req->nodesize;
328275970Scy
329275970Scy	worker_ctx = get_worker_context(c, gai_req->dns_idx);
330275970Scy	scheduled_sleep(gai_req->scheduled, gai_req->earliest,
331275970Scy			worker_ctx);
332275970Scy	reload_resolv_conf(worker_ctx);
333275970Scy
334275970Scy	/*
335275970Scy	 * Take a shot at the final size, better to overestimate
336275970Scy	 * at first and then realloc to a smaller size.
337275970Scy	 */
338275970Scy
339275970Scy	resp_octets = sizeof(*resp) + sizeof(*gai_resp) +
340275970Scy		      16 * (sizeof(struct addrinfo) +
341275970Scy			    sizeof(sockaddr_u)) +
342275970Scy		      256;
343275970Scy	resp = emalloc_zero(resp_octets);
344275970Scy	gai_resp = (void *)(resp + 1);
345275970Scy
346275970Scy	TRACE(2, ("blocking_getaddrinfo given node %s serv %s fam %d flags %x\n",
347275970Scy		  node, service, gai_req->hints.ai_family,
348275970Scy		  gai_req->hints.ai_flags));
349275970Scy#ifdef DEBUG
350275970Scy	if (debug >= 2)
351275970Scy		fflush(stdout);
352275970Scy#endif
353275970Scy	ai_res = NULL;
354275970Scy	gai_resp->retcode = getaddrinfo(node, service, &gai_req->hints,
355275970Scy					&ai_res);
356275970Scy	gai_resp->retry = gai_req->retry;
357275970Scy#ifdef EAI_SYSTEM
358275970Scy	if (EAI_SYSTEM == gai_resp->retcode)
359275970Scy		gai_resp->gai_errno = errno;
360275970Scy#endif
361275970Scy	canons_octets = 0;
362275970Scy
363275970Scy	if (0 == gai_resp->retcode) {
364275970Scy		ai = ai_res;
365275970Scy		while (NULL != ai) {
366275970Scy			gai_resp->ai_count++;
367275970Scy			if (ai->ai_canonname)
368275970Scy				canons_octets += strlen(ai->ai_canonname) + 1;
369275970Scy			ai = ai->ai_next;
370275970Scy		}
371275970Scy		/*
372275970Scy		 * If this query succeeded only after retrying, DNS may have
373275970Scy		 * just become responsive.  Ignore previously-scheduled
374275970Scy		 * retry sleeps once for each pending request, similar to
375275970Scy		 * the way scheduled_sleep() does when its worker_sleep()
376275970Scy		 * is interrupted.
377275970Scy		 */
378275970Scy		if (gai_resp->retry > INITIAL_DNS_RETRY) {
379275970Scy			time_now = time(NULL);
380275970Scy			worker_ctx->ignore_scheduled_before = time_now;
381275970Scy			TRACE(1, ("DNS success after retry, ignoring sleeps scheduled before now (%s)\n",
382275970Scy				  humantime(time_now)));
383275970Scy		}
384275970Scy	}
385275970Scy
386275970Scy	/*
387275970Scy	 * Our response consists of a header, followed by ai_count
388275970Scy	 * addrinfo structs followed by ai_count sockaddr_storage
389275970Scy	 * structs followed by the canonical names.
390275970Scy	 */
391275970Scy	gai_resp->octets = sizeof(*gai_resp)
392275970Scy			    + gai_resp->ai_count
393275970Scy				* (sizeof(gai_req->hints)
394275970Scy				   + sizeof(sockaddr_u))
395275970Scy			    + canons_octets;
396275970Scy
397275970Scy	resp_octets = sizeof(*resp) + gai_resp->octets;
398275970Scy	resp = erealloc(resp, resp_octets);
399275970Scy	gai_resp = (void *)(resp + 1);
400275970Scy
401275970Scy	/* cp serves as our current pointer while serializing */
402275970Scy	cp = (void *)(gai_resp + 1);
403275970Scy	canons_octets = 0;
404275970Scy
405275970Scy	if (0 == gai_resp->retcode) {
406275970Scy		ai = ai_res;
407275970Scy		while (NULL != ai) {
408275970Scy			memcpy(cp, ai, sizeof(*ai));
409275970Scy			serialized_ai = (void *)cp;
410275970Scy			cp += sizeof(*ai);
411275970Scy
412275970Scy			/* transform ai_canonname into offset */
413275970Scy			if (NULL != serialized_ai->ai_canonname) {
414275970Scy				serialized_ai->ai_canonname = (char *)canons_octets;
415275970Scy				canons_octets += strlen(ai->ai_canonname) + 1;
416275970Scy			}
417275970Scy
418275970Scy			/* leave fixup of ai_addr pointer for receiver */
419275970Scy
420275970Scy			ai = ai->ai_next;
421275970Scy		}
422275970Scy
423275970Scy		ai = ai_res;
424275970Scy		while (NULL != ai) {
425290000Sglebius			INSIST(ai->ai_addrlen <= sizeof(sockaddr_u));
426275970Scy			memcpy(cp, ai->ai_addr, ai->ai_addrlen);
427275970Scy			cp += sizeof(sockaddr_u);
428275970Scy
429275970Scy			ai = ai->ai_next;
430275970Scy		}
431275970Scy
432275970Scy		ai = ai_res;
433275970Scy		while (NULL != ai) {
434275970Scy			if (NULL != ai->ai_canonname) {
435275970Scy				this_octets = strlen(ai->ai_canonname) + 1;
436275970Scy				memcpy(cp, ai->ai_canonname, this_octets);
437275970Scy				cp += this_octets;
438275970Scy			}
439275970Scy
440275970Scy			ai = ai->ai_next;
441275970Scy		}
442275970Scy		freeaddrinfo(ai_res);
443275970Scy	}
444275970Scy
445275970Scy	/*
446275970Scy	 * make sure our walk and earlier calc match
447275970Scy	 */
448275970Scy	DEBUG_INSIST((size_t)(cp - (char *)resp) == resp_octets);
449275970Scy
450275970Scy	if (queue_blocking_response(c, resp, resp_octets, req)) {
451275970Scy		msyslog(LOG_ERR, "blocking_getaddrinfo can not queue response");
452275970Scy		return -1;
453275970Scy	}
454275970Scy
455275970Scy	return 0;
456275970Scy}
457275970Scy
458310419Sdelphijint
459310419Sdelphijgetaddrinfo_sometime(
460310419Sdelphij	const char *		node,
461310419Sdelphij	const char *		service,
462310419Sdelphij	const struct addrinfo *	hints,
463310419Sdelphij	int			retry,
464310419Sdelphij	gai_sometime_callback	callback,
465310419Sdelphij	void *			context
466310419Sdelphij	)
467310419Sdelphij{
468310419Sdelphij	return getaddrinfo_sometime_ex(node, service, hints, retry,
469310419Sdelphij				       callback, context, 0);
470310419Sdelphij}
471275970Scy
472310419Sdelphij
473275970Scystatic void
474275970Scygetaddrinfo_sometime_complete(
475275970Scy	blocking_work_req	rtype,
476275970Scy	void *			context,
477275970Scy	size_t			respsize,
478275970Scy	void *			resp
479275970Scy	)
480275970Scy{
481275970Scy	blocking_gai_req *	gai_req;
482275970Scy	blocking_gai_resp *	gai_resp;
483275970Scy	dnschild_ctx *		child_ctx;
484275970Scy	struct addrinfo *	ai;
485275970Scy	struct addrinfo *	next_ai;
486275970Scy	sockaddr_u *		psau;
487275970Scy	char *			node;
488275970Scy	char *			service;
489275970Scy	char *			canon_start;
490275970Scy	time_t			time_now;
491310419Sdelphij	int			again, noerr;
492275970Scy	int			af;
493275970Scy	const char *		fam_spec;
494275970Scy	int			i;
495275970Scy
496275970Scy	gai_req = context;
497275970Scy	gai_resp = resp;
498275970Scy
499275970Scy	DEBUG_REQUIRE(BLOCKING_GETADDRINFO == rtype);
500275970Scy	DEBUG_REQUIRE(respsize == gai_resp->octets);
501275970Scy
502275970Scy	node = (char *)gai_req + sizeof(*gai_req);
503275970Scy	service = node + gai_req->nodesize;
504275970Scy
505275970Scy	child_ctx = dnschild_contexts[gai_req->dns_idx];
506275970Scy
507275970Scy	if (0 == gai_resp->retcode) {
508275970Scy		/*
509275970Scy		 * If this query succeeded only after retrying, DNS may have
510275970Scy		 * just become responsive.
511275970Scy		 */
512275970Scy		if (gai_resp->retry > INITIAL_DNS_RETRY) {
513275970Scy			time_now = time(NULL);
514275970Scy			child_ctx->next_dns_timeslot = time_now;
515275970Scy			TRACE(1, ("DNS success after retry, %u next_dns_timeslot reset (%s)\n",
516275970Scy				  gai_req->dns_idx, humantime(time_now)));
517275970Scy		}
518275970Scy	} else {
519310419Sdelphij		noerr = !!(gai_req->qflags & GAIR_F_IGNDNSERR);
520310419Sdelphij		again = noerr || should_retry_dns(
521310419Sdelphij					gai_resp->retcode, gai_resp->gai_errno);
522275970Scy		/*
523275970Scy		 * exponential backoff of DNS retries to 64s
524275970Scy		 */
525275970Scy		if (gai_req->retry > 0 && again) {
526275970Scy			/* log the first retry only */
527275970Scy			if (INITIAL_DNS_RETRY == gai_req->retry)
528275970Scy				NLOG(NLOG_SYSINFO) {
529275970Scy					af = gai_req->hints.ai_family;
530275970Scy					fam_spec = (AF_INET6 == af)
531275970Scy						       ? " (AAAA)"
532275970Scy						       : (AF_INET == af)
533275970Scy							     ? " (A)"
534275970Scy							     : "";
535275970Scy#ifdef EAI_SYSTEM
536275970Scy					if (EAI_SYSTEM == gai_resp->retcode) {
537275970Scy						errno = gai_resp->gai_errno;
538275970Scy						msyslog(LOG_INFO,
539275970Scy							"retrying DNS %s%s: EAI_SYSTEM %d: %m",
540275970Scy							node, fam_spec,
541275970Scy							gai_resp->gai_errno);
542275970Scy					} else
543275970Scy#endif
544275970Scy						msyslog(LOG_INFO,
545275970Scy							"retrying DNS %s%s: %s (%d)",
546275970Scy							node, fam_spec,
547275970Scy							gai_strerror(gai_resp->retcode),
548275970Scy							gai_resp->retcode);
549275970Scy				}
550310419Sdelphij			manage_dns_retry_interval(
551310419Sdelphij				&gai_req->scheduled, &gai_req->earliest,
552310419Sdelphij				&gai_req->retry, &child_ctx->next_dns_timeslot,
553310419Sdelphij				noerr);
554275970Scy			if (!queue_blocking_request(
555275970Scy					BLOCKING_GETADDRINFO,
556275970Scy					gai_req,
557275970Scy					gai_req->octets,
558275970Scy					&getaddrinfo_sometime_complete,
559275970Scy					gai_req))
560275970Scy				return;
561275970Scy			else
562275970Scy				msyslog(LOG_ERR,
563275970Scy					"unable to retry hostname %s",
564275970Scy					node);
565275970Scy		}
566275970Scy	}
567275970Scy
568275970Scy	/*
569275970Scy	 * fixup pointers in returned addrinfo array
570275970Scy	 */
571275970Scy	ai = (void *)((char *)gai_resp + sizeof(*gai_resp));
572275970Scy	next_ai = NULL;
573275970Scy	for (i = gai_resp->ai_count - 1; i >= 0; i--) {
574275970Scy		ai[i].ai_next = next_ai;
575275970Scy		next_ai = &ai[i];
576275970Scy	}
577275970Scy
578275970Scy	psau = (void *)((char *)ai + gai_resp->ai_count * sizeof(*ai));
579275970Scy	canon_start = (char *)psau + gai_resp->ai_count * sizeof(*psau);
580275970Scy
581275970Scy	for (i = 0; i < gai_resp->ai_count; i++) {
582275970Scy		if (NULL != ai[i].ai_addr)
583275970Scy			ai[i].ai_addr = &psau->sa;
584275970Scy		psau++;
585275970Scy		if (NULL != ai[i].ai_canonname)
586275970Scy			ai[i].ai_canonname += (size_t)canon_start;
587275970Scy	}
588275970Scy
589290000Sglebius	ENSURE((char *)psau == canon_start);
590275970Scy
591275970Scy	if (!gai_resp->ai_count)
592275970Scy		ai = NULL;
593275970Scy
594275970Scy	(*gai_req->callback)(gai_resp->retcode, gai_resp->gai_errno,
595275970Scy			     gai_req->context, node, service,
596275970Scy			     &gai_req->hints, ai);
597275970Scy
598275970Scy	free(gai_req);
599275970Scy	/* gai_resp is part of block freed by process_blocking_resp() */
600275970Scy}
601275970Scy
602275970Scy
603275970Scy#ifdef TEST_BLOCKING_WORKER
604275970Scyvoid 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)
605275970Scy{
606275970Scy	sockaddr_u addr;
607275970Scy
608275970Scy	if (rescode) {
609275970Scy		TRACE(1, ("gai_test_callback context %p error rescode %d %s serv %s\n",
610275970Scy			  context, rescode, name, service));
611275970Scy		return;
612275970Scy	}
613275970Scy	while (!rescode && NULL != ai_res) {
614275970Scy		ZERO_SOCK(&addr);
615275970Scy		memcpy(&addr, ai_res->ai_addr, ai_res->ai_addrlen);
616275970Scy		TRACE(1, ("ctx %p fam %d addr %s canon '%s' type %s at %p ai_addr %p ai_next %p\n",
617275970Scy			  context,
618275970Scy			  AF(&addr),
619275970Scy			  stoa(&addr),
620275970Scy			  (ai_res->ai_canonname)
621275970Scy			      ? ai_res->ai_canonname
622275970Scy			      : "",
623275970Scy			  (SOCK_DGRAM == ai_res->ai_socktype)
624275970Scy			      ? "DGRAM"
625275970Scy			      : (SOCK_STREAM == ai_res->ai_socktype)
626275970Scy				    ? "STREAM"
627275970Scy				    : "(other)",
628275970Scy			  ai_res,
629275970Scy			  ai_res->ai_addr,
630275970Scy			  ai_res->ai_next));
631275970Scy
632275970Scy		getnameinfo_sometime((sockaddr_u *)ai_res->ai_addr, 128, 32, 0, gni_test_callback, context);
633275970Scy
634275970Scy		ai_res = ai_res->ai_next;
635275970Scy	}
636275970Scy}
637275970Scy#endif	/* TEST_BLOCKING_WORKER */
638275970Scy
639275970Scy
640275970Scyint
641275970Scygetnameinfo_sometime(
642275970Scy	sockaddr_u *		psau,
643275970Scy	size_t			hostoctets,
644275970Scy	size_t			servoctets,
645275970Scy	int			flags,
646275970Scy	gni_sometime_callback	callback,
647275970Scy	void *			context
648275970Scy	)
649275970Scy{
650275970Scy	blocking_gni_req *	gni_req;
651275970Scy	u_int			idx;
652275970Scy	dnschild_ctx *		child_ctx;
653275970Scy	time_t			time_now;
654275970Scy
655290000Sglebius	REQUIRE(hostoctets);
656290000Sglebius	REQUIRE(hostoctets + servoctets < 1024);
657275970Scy
658275970Scy	idx = get_dnschild_ctx();
659275970Scy	child_ctx = dnschild_contexts[idx];
660275970Scy
661275970Scy	gni_req = emalloc_zero(sizeof(*gni_req));
662275970Scy
663275970Scy	gni_req->octets = sizeof(*gni_req);
664275970Scy	gni_req->dns_idx = idx;
665275970Scy	time_now = time(NULL);
666275970Scy	gni_req->scheduled = time_now;
667275970Scy	gni_req->earliest = max(time_now, child_ctx->next_dns_timeslot);
668275970Scy	child_ctx->next_dns_timeslot = gni_req->earliest;
669275970Scy	memcpy(&gni_req->socku, psau, SOCKLEN(psau));
670275970Scy	gni_req->hostoctets = hostoctets;
671275970Scy	gni_req->servoctets = servoctets;
672275970Scy	gni_req->flags = flags;
673275970Scy	gni_req->retry = INITIAL_DNS_RETRY;
674275970Scy	gni_req->callback = callback;
675275970Scy	gni_req->context = context;
676275970Scy
677275970Scy	if (queue_blocking_request(
678275970Scy		BLOCKING_GETNAMEINFO,
679275970Scy		gni_req,
680275970Scy		sizeof(*gni_req),
681275970Scy		&getnameinfo_sometime_complete,
682275970Scy		gni_req)) {
683275970Scy
684275970Scy		msyslog(LOG_ERR, "unable to queue getnameinfo request");
685275970Scy		errno = EFAULT;
686275970Scy		return -1;
687275970Scy	}
688275970Scy
689275970Scy	return 0;
690275970Scy}
691275970Scy
692275970Scy
693275970Scyint
694275970Scyblocking_getnameinfo(
695275970Scy	blocking_child *	c,
696275970Scy	blocking_pipe_header *	req
697275970Scy	)
698275970Scy{
699275970Scy	blocking_gni_req *	gni_req;
700275970Scy	dnsworker_ctx *		worker_ctx;
701275970Scy	blocking_pipe_header *	resp;
702275970Scy	blocking_gni_resp *	gni_resp;
703275970Scy	size_t			octets;
704275970Scy	size_t			resp_octets;
705275970Scy	char *			service;
706275970Scy	char *			cp;
707275970Scy	int			rc;
708275970Scy	time_t			time_now;
709280849Scy	char			host[1024];
710275970Scy
711275970Scy	gni_req = (void *)((char *)req + sizeof(*req));
712275970Scy
713275970Scy	octets = gni_req->hostoctets + gni_req->servoctets;
714275970Scy
715275970Scy	/*
716275970Scy	 * Some alloca() implementations are fragile regarding
717275970Scy	 * large allocations.  We only need room for the host
718275970Scy	 * and service names.
719275970Scy	 */
720290000Sglebius	REQUIRE(octets < sizeof(host));
721275970Scy	service = host + gni_req->hostoctets;
722275970Scy
723275970Scy	worker_ctx = get_worker_context(c, gni_req->dns_idx);
724275970Scy	scheduled_sleep(gni_req->scheduled, gni_req->earliest,
725275970Scy			worker_ctx);
726275970Scy	reload_resolv_conf(worker_ctx);
727275970Scy
728275970Scy	/*
729275970Scy	 * Take a shot at the final size, better to overestimate
730275970Scy	 * then realloc to a smaller size.
731275970Scy	 */
732275970Scy
733275970Scy	resp_octets = sizeof(*resp) + sizeof(*gni_resp) + octets;
734275970Scy	resp = emalloc_zero(resp_octets);
735275970Scy	gni_resp = (void *)((char *)resp + sizeof(*resp));
736275970Scy
737275970Scy	TRACE(2, ("blocking_getnameinfo given addr %s flags 0x%x hostlen %lu servlen %lu\n",
738275970Scy		  stoa(&gni_req->socku), gni_req->flags,
739275970Scy		  (u_long)gni_req->hostoctets, (u_long)gni_req->servoctets));
740275970Scy
741275970Scy	gni_resp->retcode = getnameinfo(&gni_req->socku.sa,
742275970Scy					SOCKLEN(&gni_req->socku),
743275970Scy					host,
744275970Scy					gni_req->hostoctets,
745275970Scy					service,
746275970Scy					gni_req->servoctets,
747275970Scy					gni_req->flags);
748275970Scy	gni_resp->retry = gni_req->retry;
749275970Scy#ifdef EAI_SYSTEM
750275970Scy	if (EAI_SYSTEM == gni_resp->retcode)
751275970Scy		gni_resp->gni_errno = errno;
752275970Scy#endif
753275970Scy
754275970Scy	if (0 != gni_resp->retcode) {
755275970Scy		gni_resp->hostoctets = 0;
756275970Scy		gni_resp->servoctets = 0;
757275970Scy	} else {
758275970Scy		gni_resp->hostoctets = strlen(host) + 1;
759275970Scy		gni_resp->servoctets = strlen(service) + 1;
760275970Scy		/*
761275970Scy		 * If this query succeeded only after retrying, DNS may have
762275970Scy		 * just become responsive.  Ignore previously-scheduled
763275970Scy		 * retry sleeps once for each pending request, similar to
764275970Scy		 * the way scheduled_sleep() does when its worker_sleep()
765275970Scy		 * is interrupted.
766275970Scy		 */
767275970Scy		if (gni_req->retry > INITIAL_DNS_RETRY) {
768275970Scy			time_now = time(NULL);
769275970Scy			worker_ctx->ignore_scheduled_before = time_now;
770275970Scy			TRACE(1, ("DNS success after retrying, ignoring sleeps scheduled before now (%s)\n",
771275970Scy				humantime(time_now)));
772275970Scy		}
773275970Scy	}
774275970Scy	octets = gni_resp->hostoctets + gni_resp->servoctets;
775275970Scy	/*
776275970Scy	 * Our response consists of a header, followed by the host and
777275970Scy	 * service strings, each null-terminated.
778275970Scy	 */
779275970Scy	resp_octets = sizeof(*resp) + sizeof(*gni_resp) + octets;
780275970Scy
781275970Scy	resp = erealloc(resp, resp_octets);
782275970Scy	gni_resp = (void *)(resp + 1);
783275970Scy
784275970Scy	gni_resp->octets = sizeof(*gni_resp) + octets;
785275970Scy
786275970Scy	/* cp serves as our current pointer while serializing */
787275970Scy	cp = (void *)(gni_resp + 1);
788275970Scy
789275970Scy	if (0 == gni_resp->retcode) {
790275970Scy		memcpy(cp, host, gni_resp->hostoctets);
791275970Scy		cp += gni_resp->hostoctets;
792275970Scy		memcpy(cp, service, gni_resp->servoctets);
793275970Scy		cp += gni_resp->servoctets;
794275970Scy	}
795275970Scy
796290000Sglebius	INSIST((size_t)(cp - (char *)resp) == resp_octets);
797290000Sglebius	INSIST(resp_octets - sizeof(*resp) == gni_resp->octets);
798275970Scy
799275970Scy	rc = queue_blocking_response(c, resp, resp_octets, req);
800275970Scy	if (rc)
801275970Scy		msyslog(LOG_ERR, "blocking_getnameinfo unable to queue response");
802275970Scy	return rc;
803275970Scy}
804275970Scy
805275970Scy
806275970Scystatic void
807275970Scygetnameinfo_sometime_complete(
808275970Scy	blocking_work_req	rtype,
809275970Scy	void *			context,
810275970Scy	size_t			respsize,
811275970Scy	void *			resp
812275970Scy	)
813275970Scy{
814275970Scy	blocking_gni_req *	gni_req;
815275970Scy	blocking_gni_resp *	gni_resp;
816275970Scy	dnschild_ctx *		child_ctx;
817275970Scy	char *			host;
818275970Scy	char *			service;
819275970Scy	time_t			time_now;
820275970Scy	int			again;
821275970Scy
822275970Scy	gni_req = context;
823275970Scy	gni_resp = resp;
824275970Scy
825275970Scy	DEBUG_REQUIRE(BLOCKING_GETNAMEINFO == rtype);
826275970Scy	DEBUG_REQUIRE(respsize == gni_resp->octets);
827275970Scy
828275970Scy	child_ctx = dnschild_contexts[gni_req->dns_idx];
829275970Scy
830275970Scy	if (0 == gni_resp->retcode) {
831275970Scy		/*
832275970Scy		 * If this query succeeded only after retrying, DNS may have
833275970Scy		 * just become responsive.
834275970Scy		 */
835275970Scy		if (gni_resp->retry > INITIAL_DNS_RETRY) {
836275970Scy			time_now = time(NULL);
837275970Scy			child_ctx->next_dns_timeslot = time_now;
838275970Scy			TRACE(1, ("DNS success after retry, %u next_dns_timeslot reset (%s)\n",
839275970Scy				  gni_req->dns_idx, humantime(time_now)));
840275970Scy		}
841275970Scy	} else {
842275970Scy		again = should_retry_dns(gni_resp->retcode, gni_resp->gni_errno);
843275970Scy		/*
844275970Scy		 * exponential backoff of DNS retries to 64s
845275970Scy		 */
846275970Scy		if (gni_req->retry > 0)
847275970Scy			manage_dns_retry_interval(&gni_req->scheduled,
848275970Scy			    &gni_req->earliest, &gni_req->retry,
849310419Sdelphij						  &child_ctx->next_dns_timeslot, FALSE);
850275970Scy
851275970Scy		if (gni_req->retry > 0 && again) {
852275970Scy			if (!queue_blocking_request(
853275970Scy				BLOCKING_GETNAMEINFO,
854275970Scy				gni_req,
855275970Scy				gni_req->octets,
856275970Scy				&getnameinfo_sometime_complete,
857275970Scy				gni_req))
858275970Scy				return;
859275970Scy
860275970Scy			msyslog(LOG_ERR, "unable to retry reverse lookup of %s", stoa(&gni_req->socku));
861275970Scy		}
862275970Scy	}
863275970Scy
864275970Scy	if (!gni_resp->hostoctets) {
865275970Scy		host = NULL;
866275970Scy		service = NULL;
867275970Scy	} else {
868275970Scy		host = (char *)gni_resp + sizeof(*gni_resp);
869275970Scy		service = (gni_resp->servoctets)
870275970Scy			      ? host + gni_resp->hostoctets
871275970Scy			      : NULL;
872275970Scy	}
873275970Scy
874275970Scy	(*gni_req->callback)(gni_resp->retcode, gni_resp->gni_errno,
875275970Scy			     &gni_req->socku, gni_req->flags, host,
876275970Scy			     service, gni_req->context);
877275970Scy
878275970Scy	free(gni_req);
879275970Scy	/* gni_resp is part of block freed by process_blocking_resp() */
880275970Scy}
881275970Scy
882275970Scy
883275970Scy#ifdef TEST_BLOCKING_WORKER
884275970Scyvoid gni_test_callback(int rescode, int gni_errno, sockaddr_u *psau, int flags, const char *host, const char *service, void *context)
885275970Scy{
886275970Scy	if (!rescode)
887275970Scy		TRACE(1, ("gni_test_callback got host '%s' serv '%s' for addr %s context %p\n",
888275970Scy			  host, service, stoa(psau), context));
889275970Scy	else
890275970Scy		TRACE(1, ("gni_test_callback context %p rescode %d gni_errno %d flags 0x%x addr %s\n",
891275970Scy			  context, rescode, gni_errno, flags, stoa(psau)));
892275970Scy}
893275970Scy#endif	/* TEST_BLOCKING_WORKER */
894275970Scy
895275970Scy
896275970Scy#ifdef HAVE_RES_INIT
897275970Scystatic void
898275970Scyreload_resolv_conf(
899275970Scy	dnsworker_ctx *	worker_ctx
900275970Scy	)
901275970Scy{
902275970Scy	time_t	time_now;
903275970Scy
904275970Scy	/*
905275970Scy	 * This is ad-hoc.  Reload /etc/resolv.conf once per minute
906275970Scy	 * to pick up on changes from the DHCP client.  [Bug 1226]
907275970Scy	 * When using threads for the workers, this needs to happen
908275970Scy	 * only once per minute process-wide.
909275970Scy	 */
910275970Scy	time_now = time(NULL);
911275970Scy# ifdef WORK_THREAD
912275970Scy	worker_ctx->next_res_init = next_res_init;
913275970Scy# endif
914275970Scy	if (worker_ctx->next_res_init <= time_now) {
915275970Scy		if (worker_ctx->next_res_init != 0)
916275970Scy			res_init();
917275970Scy		worker_ctx->next_res_init = time_now + 60;
918275970Scy# ifdef WORK_THREAD
919275970Scy		next_res_init = worker_ctx->next_res_init;
920275970Scy# endif
921275970Scy	}
922275970Scy}
923275970Scy#endif	/* HAVE_RES_INIT */
924275970Scy
925275970Scy
926275970Scystatic u_int
927275970Scyreserve_dnschild_ctx(void)
928275970Scy{
929275970Scy	const size_t	ps = sizeof(dnschild_contexts[0]);
930275970Scy	const size_t	cs = sizeof(*dnschild_contexts[0]);
931275970Scy	u_int		c;
932275970Scy	u_int		new_alloc;
933275970Scy	size_t		octets;
934275970Scy	size_t		new_octets;
935275970Scy
936275970Scy	c = 0;
937275970Scy	while (TRUE) {
938275970Scy		for ( ; c < dnschild_contexts_alloc; c++) {
939275970Scy			if (NULL == dnschild_contexts[c]) {
940275970Scy				dnschild_contexts[c] = emalloc_zero(cs);
941275970Scy
942275970Scy				return c;
943275970Scy			}
944275970Scy		}
945275970Scy		new_alloc = dnschild_contexts_alloc + 20;
946275970Scy		new_octets = new_alloc * ps;
947275970Scy		octets = dnschild_contexts_alloc * ps;
948275970Scy		dnschild_contexts = erealloc_zero(dnschild_contexts,
949275970Scy						  new_octets, octets);
950275970Scy		dnschild_contexts_alloc = new_alloc;
951275970Scy	}
952275970Scy}
953275970Scy
954275970Scy
955275970Scystatic u_int
956275970Scyget_dnschild_ctx(void)
957275970Scy{
958275970Scy	static u_int	shared_ctx = UINT_MAX;
959275970Scy
960275970Scy	if (worker_per_query)
961275970Scy		return reserve_dnschild_ctx();
962275970Scy
963275970Scy	if (UINT_MAX == shared_ctx)
964275970Scy		shared_ctx = reserve_dnschild_ctx();
965275970Scy
966275970Scy	return shared_ctx;
967275970Scy}
968275970Scy
969275970Scy
970275970Scystatic dnsworker_ctx *
971275970Scyget_worker_context(
972275970Scy	blocking_child *	c,
973275970Scy	u_int			idx
974275970Scy	)
975275970Scy{
976298770Sdelphij	u_int		min_new_alloc;
977298770Sdelphij	u_int		new_alloc;
978298770Sdelphij	size_t		octets;
979298770Sdelphij	size_t		new_octets;
980298770Sdelphij	dnsworker_ctx *	retv;
981275970Scy
982298770Sdelphij	worker_global_lock(TRUE);
983298770Sdelphij
984275970Scy	if (dnsworker_contexts_alloc <= idx) {
985275970Scy		min_new_alloc = 1 + idx;
986275970Scy		/* round new_alloc up to nearest multiple of 4 */
987275970Scy		new_alloc = (min_new_alloc + 4) & ~(4 - 1);
988298770Sdelphij		new_octets = new_alloc * sizeof(dnsworker_ctx*);
989298770Sdelphij		octets = dnsworker_contexts_alloc * sizeof(dnsworker_ctx*);
990275970Scy		dnsworker_contexts = erealloc_zero(dnsworker_contexts,
991275970Scy						   new_octets, octets);
992275970Scy		dnsworker_contexts_alloc = new_alloc;
993298770Sdelphij		retv = emalloc_zero(sizeof(dnsworker_ctx));
994298770Sdelphij		dnsworker_contexts[idx] = retv;
995298770Sdelphij	} else if (NULL == (retv = dnsworker_contexts[idx])) {
996298770Sdelphij		retv = emalloc_zero(sizeof(dnsworker_ctx));
997298770Sdelphij		dnsworker_contexts[idx] = retv;
998275970Scy	}
999298770Sdelphij
1000298770Sdelphij	worker_global_lock(FALSE);
1001298770Sdelphij
1002298770Sdelphij	ZERO(*retv);
1003298770Sdelphij	retv->c = c;
1004298770Sdelphij	return retv;
1005275970Scy}
1006275970Scy
1007275970Scy
1008275970Scystatic void
1009275970Scyscheduled_sleep(
1010275970Scy	time_t		scheduled,
1011275970Scy	time_t		earliest,
1012275970Scy	dnsworker_ctx *	worker_ctx
1013275970Scy	)
1014275970Scy{
1015275970Scy	time_t now;
1016275970Scy
1017275970Scy	if (scheduled < worker_ctx->ignore_scheduled_before) {
1018275970Scy		TRACE(1, ("ignoring sleep until %s scheduled at %s (before %s)\n",
1019275970Scy			  humantime(earliest), humantime(scheduled),
1020275970Scy			  humantime(worker_ctx->ignore_scheduled_before)));
1021275970Scy		return;
1022275970Scy	}
1023275970Scy
1024275970Scy	now = time(NULL);
1025275970Scy
1026275970Scy	if (now < earliest) {
1027275970Scy		TRACE(1, ("sleep until %s scheduled at %s (>= %s)\n",
1028275970Scy			  humantime(earliest), humantime(scheduled),
1029275970Scy			  humantime(worker_ctx->ignore_scheduled_before)));
1030275970Scy		if (-1 == worker_sleep(worker_ctx->c, earliest - now)) {
1031275970Scy			/* our sleep was interrupted */
1032275970Scy			now = time(NULL);
1033275970Scy			worker_ctx->ignore_scheduled_before = now;
1034275970Scy#ifdef HAVE_RES_INIT
1035275970Scy			worker_ctx->next_res_init = now + 60;
1036275970Scy			next_res_init = worker_ctx->next_res_init;
1037275970Scy			res_init();
1038275970Scy#endif
1039275970Scy			TRACE(1, ("sleep interrupted by daemon, ignoring sleeps scheduled before now (%s)\n",
1040275970Scy				  humantime(worker_ctx->ignore_scheduled_before)));
1041275970Scy		}
1042275970Scy	}
1043275970Scy}
1044275970Scy
1045275970Scy
1046275970Scy/*
1047275970Scy * manage_dns_retry_interval is a helper used by
1048275970Scy * getaddrinfo_sometime_complete and getnameinfo_sometime_complete
1049275970Scy * to calculate the new retry interval and schedule the next query.
1050275970Scy */
1051275970Scystatic void
1052275970Scymanage_dns_retry_interval(
1053275970Scy	time_t *	pscheduled,
1054275970Scy	time_t *	pwhen,
1055275970Scy	int *		pretry,
1056310419Sdelphij	time_t *	pnext_timeslot,
1057310419Sdelphij	int		forever
1058275970Scy	)
1059275970Scy{
1060275970Scy	time_t	now;
1061275970Scy	time_t	when;
1062275970Scy	int	retry;
1063310419Sdelphij	int	retmax;
1064275970Scy
1065275970Scy	now = time(NULL);
1066275970Scy	retry = *pretry;
1067275970Scy	when = max(now + retry, *pnext_timeslot);
1068275970Scy	*pnext_timeslot = when;
1069275970Scy
1070310419Sdelphij	/* this exponential backoff is slower than doubling up: The
1071310419Sdelphij	 * sequence goes 2-3-4-6-8-12-16-24-32... and the upper limit is
1072310419Sdelphij	 * 64 seconds for things that should not repeat forever, and
1073310419Sdelphij	 * 1024 when repeated forever.
1074310419Sdelphij	 */
1075310419Sdelphij	retmax = forever ? 1024 : 64;
1076310419Sdelphij	retry <<= 1;
1077310419Sdelphij	if (retry & (retry - 1))
1078310419Sdelphij		retry &= (retry - 1);
1079310419Sdelphij	else
1080310419Sdelphij		retry -= (retry >> 2);
1081310419Sdelphij	retry = min(retmax, retry);
1082310419Sdelphij
1083275970Scy	*pscheduled = now;
1084275970Scy	*pwhen = when;
1085275970Scy	*pretry = retry;
1086275970Scy}
1087275970Scy
1088275970Scy/*
1089275970Scy * should_retry_dns is a helper used by getaddrinfo_sometime_complete
1090275970Scy * and getnameinfo_sometime_complete which implements ntpd's DNS retry
1091275970Scy * policy.
1092275970Scy */
1093275970Scystatic int
1094275970Scyshould_retry_dns(
1095275970Scy	int	rescode,
1096275970Scy	int	res_errno
1097275970Scy	)
1098275970Scy{
1099275970Scy	static int	eai_again_seen;
1100275970Scy	int		again;
1101275970Scy#if defined (EAI_SYSTEM) && defined(DEBUG)
1102275970Scy	char		msg[256];
1103275970Scy#endif
1104275970Scy
1105275970Scy	/*
1106275970Scy	 * If the resolver failed, see if the failure is
1107275970Scy	 * temporary. If so, return success.
1108275970Scy	 */
1109275970Scy	again = 0;
1110275970Scy
1111275970Scy	switch (rescode) {
1112275970Scy
1113275970Scy	case EAI_FAIL:
1114275970Scy		again = 1;
1115275970Scy		break;
1116275970Scy
1117275970Scy	case EAI_AGAIN:
1118275970Scy		again = 1;
1119275970Scy		eai_again_seen = 1;		/* [Bug 1178] */
1120275970Scy		break;
1121275970Scy
1122275970Scy	case EAI_NONAME:
1123275970Scy#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
1124275970Scy	case EAI_NODATA:
1125275970Scy#endif
1126275970Scy		again = !eai_again_seen;	/* [Bug 1178] */
1127275970Scy		break;
1128275970Scy
1129275970Scy#ifdef EAI_SYSTEM
1130275970Scy	case EAI_SYSTEM:
1131275970Scy		/*
1132275970Scy		 * EAI_SYSTEM means the real error is in errno.  We should be more
1133275970Scy		 * discriminating about which errno values require retrying, but
1134275970Scy		 * this matches existing behavior.
1135275970Scy		 */
1136275970Scy		again = 1;
1137275970Scy# ifdef DEBUG
1138275970Scy		errno_to_str(res_errno, msg, sizeof(msg));
1139275970Scy		TRACE(1, ("intres: EAI_SYSTEM errno %d (%s) means try again, right?\n",
1140275970Scy			  res_errno, msg));
1141275970Scy# endif
1142275970Scy		break;
1143275970Scy#endif
1144275970Scy	}
1145275970Scy
1146275970Scy	TRACE(2, ("intres: resolver returned: %s (%d), %sretrying\n",
1147275970Scy		  gai_strerror(rescode), rescode, again ? "" : "not "));
1148275970Scy
1149275970Scy	return again;
1150275970Scy}
1151275970Scy
1152275970Scy#else	/* !WORKER follows */
1153275970Scyint ntp_intres_nonempty_compilation_unit;
1154275970Scy#endif
1155