xref: /openbsd-src/lib/libc/asr/getaddrinfo_async.c (revision beabf0626f373e6ad006167ab8b5eb7203d3b5e4)
1 /*	$OpenBSD: getaddrinfo_async.c,v 1.2 2012/04/25 20:28:25 eric Exp $	*/
2 /*
3  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/types.h>
18 #include <sys/uio.h>
19 
20 #include <arpa/nameser.h>
21 
22 #include <err.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "asr.h"
29 #include "asr_private.h"
30 
31 struct match {
32 	int family;
33 	int socktype;
34 	int protocol;
35 };
36 
37 static int getaddrinfo_async_run(struct async *, struct async_res *);
38 static int get_port(const char *, const char *, int);
39 static int iter_family(struct async *, int);
40 static int add_sockaddr(struct async *, struct sockaddr *, const char *);
41 
42 static const struct match matches[] = {
43 	{ PF_INET,	SOCK_DGRAM,	IPPROTO_UDP	},
44 	{ PF_INET,	SOCK_STREAM,	IPPROTO_TCP	},
45 	{ PF_INET,	SOCK_RAW,	0		},
46 	{ PF_INET6,	SOCK_DGRAM,	IPPROTO_UDP	},
47 	{ PF_INET6,	SOCK_STREAM,	IPPROTO_TCP	},
48 	{ PF_INET6,	SOCK_RAW,	0		},
49 	{ -1, 		0, 		0, 		},
50 };
51 
52 #define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC)
53 #define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0)
54 /* Do not match SOCK_RAW unless explicitely specified */
55 #define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \
56 				matches[(b)].socktype != SOCK_RAW))
57 
58 struct async *
59 getaddrinfo_async(const char *hostname, const char *servname,
60 	const struct addrinfo *hints, struct asr *asr)
61 {
62 	struct asr_ctx	*ac;
63 	struct async	*as;
64 
65 	ac = asr_use_resolver(asr);
66 	if ((as = async_new(ac, ASR_GETADDRINFO)) == NULL)
67 		goto abort; /* errno set */
68 	as->as_run = getaddrinfo_async_run;
69 
70 	if (hostname && (as->as.ai.hostname = strdup(hostname)) == NULL)
71 		goto abort; /* errno set */
72 	if (servname && (as->as.ai.servname = strdup(servname)) == NULL)
73 		goto abort; /* errno set */
74 	if (hints)
75 		memmove(&as->as.ai.hints, hints, sizeof *hints);
76 	else {
77 		memset(&as->as.ai.hints, 0, sizeof as->as.ai.hints);
78 		as->as.ai.hints.ai_family = PF_UNSPEC;
79 	}
80 
81 	asr_ctx_unref(ac);
82 	return (as);
83     abort:
84 	if (as)
85 		async_free(as);
86 	asr_ctx_unref(ac);
87 	return (NULL);
88 }
89 
90 static int
91 getaddrinfo_async_run(struct async *as, struct async_res *ar)
92 {
93 	const char	*str;
94 	struct addrinfo	*ai;
95 	int		 i, family, r;
96 	char		 fqdn[MAXDNAME];
97 	union {
98 		struct sockaddr		sa;
99 		struct sockaddr_in	sain;
100 		struct sockaddr_in6	sain6;
101 	} sa;
102 
103     next:
104 	switch(as->as_state) {
105 
106 	case ASR_STATE_INIT:
107 
108 		/*
109 		 * First, make sure the parameters are valid.
110 		 */
111 
112 		as->as_count = 0;
113 		async_set_state(as, ASR_STATE_HALT);
114 		ar->ar_errno = 0;
115 		ar->ar_h_errno = NETDB_SUCCESS;
116 		ar->ar_gai_errno = 0;
117 
118 		if (as->as.ai.hostname == NULL &&
119 		    as->as.ai.servname == NULL) {
120 			ar->ar_h_errno = NO_RECOVERY;
121 			ar->ar_gai_errno = EAI_NONAME;
122 			break;
123 		}
124 
125 		ai = &as->as.ai.hints;
126 
127 		if (ai->ai_addrlen ||
128 		    ai->ai_canonname ||
129 		    ai->ai_addr ||
130 		    ai->ai_next) {
131 			ar->ar_h_errno = NO_RECOVERY;
132 			ar->ar_gai_errno = EAI_BADHINTS;
133 			break;
134 		}
135 
136 		if (ai->ai_flags & ~AI_MASK ||
137 		    (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) {
138 			ar->ar_h_errno = NO_RECOVERY;
139 			ar->ar_gai_errno = EAI_BADFLAGS;
140 			break;
141 		}
142 
143 		if (ai->ai_family != PF_UNSPEC &&
144 		    ai->ai_family != PF_INET &&
145 		    ai->ai_family != PF_INET6) {
146 			ar->ar_h_errno = NO_RECOVERY;
147 			ar->ar_gai_errno = EAI_FAMILY;
148 			break;
149 		}
150 
151 		if (ai->ai_socktype &&
152 		    ai->ai_socktype != SOCK_DGRAM  &&
153 		    ai->ai_socktype != SOCK_STREAM &&
154 		    ai->ai_socktype != SOCK_RAW) {
155 			ar->ar_h_errno = NO_RECOVERY;
156 			ar->ar_gai_errno = EAI_SOCKTYPE;
157 			break;
158 		}
159 
160 		if (ai->ai_protocol &&
161 		    ai->ai_protocol != IPPROTO_UDP  &&
162 		    ai->ai_protocol != IPPROTO_TCP) {
163 			ar->ar_h_errno = NO_RECOVERY;
164 			ar->ar_gai_errno = EAI_PROTOCOL;
165 			break;
166 		}
167 
168 		if (ai->ai_socktype == SOCK_RAW &&
169 		    as->as.ai.servname != NULL) {
170 			ar->ar_h_errno = NO_RECOVERY;
171 			ar->ar_gai_errno = EAI_SERVICE;
172 			break;
173 		}
174 
175 		/* Make sure there is at least a valid combination */
176 		for (i = 0; matches[i].family != -1; i++)
177 			if (MATCH_FAMILY(ai->ai_family, i) &&
178 			    MATCH_SOCKTYPE(ai->ai_socktype, i) &&
179 			    MATCH_PROTO(ai->ai_protocol, i))
180 				break;
181 		if (matches[i].family == -1) {
182 			ar->ar_h_errno = NO_RECOVERY;
183 			ar->ar_gai_errno = EAI_BADHINTS;
184 			break;
185 		}
186 
187 		if (as->as.ai.servname) {
188 			as->as.ai.port_udp = get_port(as->as.ai.servname,
189 			    "udp", as->as.ai.hints.ai_flags & AI_NUMERICSERV);
190 			as->as.ai.port_tcp = get_port(as->as.ai.servname,
191 			    "tcp", as->as.ai.hints.ai_flags & AI_NUMERICSERV);
192 			if (as->as.ai.port_tcp < 0 || as->as.ai.port_udp < 0) {
193 				ar->ar_h_errno = NO_RECOVERY;
194 				ar->ar_gai_errno = EAI_SERVICE;
195 				break;
196 			}
197 		}
198 
199 		/* If hostname is NULL, use local address */
200 		if (as->as.ai.hostname == NULL) {
201 			for(family = iter_family(as, 1);
202 			    family != -1;
203 			    family = iter_family(as, 0)) {
204 				/*
205 				 * We could use statically built sockaddrs for
206 				 * those, rather than parsing over and over.
207 				 */
208 				if (family == PF_INET)
209 					str = (ai->ai_flags & AI_PASSIVE) ? \
210 						"0.0.0.0" : "127.0.0.1";
211 				else /* PF_INET6 */
212 					str = (ai->ai_flags & AI_PASSIVE) ? \
213 						"::" : "::1";
214 				 /* This can't fail */
215 				sockaddr_from_str(&sa.sa, family, str);
216 				if ((r = add_sockaddr(as, &sa.sa, NULL))) {
217 					ar->ar_errno = errno;
218 					ar->ar_h_errno = NETDB_INTERNAL;
219 					ar->ar_gai_errno = r;
220 					async_set_state(as, ASR_STATE_HALT);
221 					break;
222 				}
223 			}
224 			if (ar->ar_gai_errno == 0 && as->as_count == 0) {
225 				ar->ar_h_errno = NO_DATA;
226 				ar->ar_gai_errno = EAI_NODATA;
227 			}
228 			break;
229 		}
230 
231 		/* Try numeric addresses first */
232 		for(family = iter_family(as, 1);
233 		    family != -1;
234 		    family = iter_family(as, 0)) {
235 
236 			if (sockaddr_from_str(&sa.sa, family,
237 					      as->as.ai.hostname) == -1)
238 				continue;
239 
240 			if ((r = add_sockaddr(as, &sa.sa, NULL))) {
241 				ar->ar_errno = errno;
242 				ar->ar_h_errno = NETDB_INTERNAL;
243 				ar->ar_gai_errno = r;
244 				async_set_state(as, ASR_STATE_HALT);
245 				break;
246 			}
247 
248 			async_set_state(as, ASR_STATE_HALT);
249 			break;
250 		}
251 		if (ar->ar_gai_errno || as->as_count)
252 			break;
253 
254 		if (ai->ai_flags & AI_NUMERICHOST) {
255 			ar->ar_h_errno = NO_RECOVERY;
256 			ar->ar_gai_errno = EAI_FAIL;
257 			async_set_state(as, ASR_STATE_HALT);
258 			break;
259 		}
260 
261 		/* Starting domain lookup */
262 		async_set_state(as, ASR_STATE_SEARCH_DOMAIN);
263 		break;
264 
265 	case ASR_STATE_SEARCH_DOMAIN:
266 
267 		r = asr_iter_domain(as, as->as.ai.hostname, fqdn, sizeof(fqdn));
268 		if (r == -1) {
269 			async_set_state(as, ASR_STATE_NOT_FOUND);
270 			break;
271 		}
272 		if (r > (int)sizeof(fqdn)) {
273 			ar->ar_errno = EINVAL;
274 			ar->ar_h_errno = NO_RECOVERY;
275 			ar->ar_gai_errno = EAI_OVERFLOW;
276 			async_set_state(as, ASR_STATE_HALT);
277 			break;
278 		}
279 
280 		/*
281 		 * Create a subquery to lookup the host addresses.
282 		 * We use the special hostaddr_async() API, which has the
283 		 * nice property of honoring the "lookup" and "family" keyword
284 		 * in the configuration, thus returning the right address
285 		 * families in the right order, and thus fixing the current
286 		 * getaddrinfo() feature documented in the BUGS section of
287 		 * resolver.conf(5).
288 		 */
289 		as->as.ai.subq = hostaddr_async_ctx(fqdn,
290 		    as->as.ai.hints.ai_family, as->as.ai.hints.ai_flags,
291 		    as->as_ctx);
292 		if (as->as.ai.subq == NULL) {
293 			ar->ar_errno = errno;
294 			if (errno == EINVAL) {
295 				ar->ar_h_errno = NO_RECOVERY;
296 				ar->ar_gai_errno = EAI_FAIL;
297 			} else {
298 				ar->ar_h_errno = NETDB_INTERNAL;
299 				ar->ar_gai_errno = EAI_MEMORY;
300 			}
301 			async_set_state(as, ASR_STATE_HALT);
302 			break;
303 		}
304 		async_set_state(as, ASR_STATE_LOOKUP_DOMAIN);
305 		break;
306 
307 	case ASR_STATE_LOOKUP_DOMAIN:
308 
309 		/* Run the subquery */
310 		if ((r = async_run(as->as.ai.subq, ar)) == ASYNC_COND)
311 			return (ASYNC_COND);
312 
313 		/* Got one more address, use it to extend the result list. */
314 		if (r == ASYNC_YIELD) {
315 			if ((r = add_sockaddr(as, &ar->ar_sa.sa,
316 			    ar->ar_cname))) {
317 				ar->ar_errno = errno;
318 				ar->ar_h_errno = NETDB_INTERNAL;
319 				ar->ar_gai_errno = r;
320 				async_set_state(as, ASR_STATE_HALT);
321 			}
322 			if (ar->ar_cname)
323 				free(ar->ar_cname);
324 			break;
325 		}
326 
327 		/*
328 		 * The subquery is done. Stop there if we have at least one
329 		 * answer.
330 		 */
331 		as->as.ai.subq = NULL;
332 		if (ar->ar_count) {
333 			async_set_state(as, ASR_STATE_HALT);
334 			break;
335 		}
336 
337 		/*
338 		 * No anwser for this domain, but we might be suggested to
339 		 * try again later, so remember this. Then search the next
340 		 * domain.
341 		 */
342 		if (ar->ar_gai_errno == EAI_AGAIN)
343 			as->as.ai.flags |= ASYNC_AGAIN;
344 		async_set_state(as, ASR_STATE_SEARCH_DOMAIN);
345 		break;
346 
347 	case ASR_STATE_NOT_FOUND:
348 
349 		/*
350 		 * No result found. Maybe we can try again.
351 		 */
352 		ar->ar_errno = 0;
353 		if (as->as.ai.flags & ASYNC_AGAIN) {
354 			ar->ar_h_errno = TRY_AGAIN;
355 			ar->ar_gai_errno = EAI_AGAIN;
356 		} else {
357 			ar->ar_h_errno = NO_DATA;
358 			ar->ar_gai_errno = EAI_NODATA;
359 		}
360 		async_set_state(as, ASR_STATE_HALT);
361 		break;
362 
363 	case ASR_STATE_HALT:
364 
365 		/* Set the results. */
366 
367 		if (ar->ar_gai_errno == 0) {
368 			ar->ar_count = as->as_count;
369 			ar->ar_addrinfo = as->as.ai.aifirst;
370 			as->as.ai.aifirst = NULL;
371 		} else {
372 			ar->ar_count = 0;
373 			ar->ar_addrinfo = NULL;
374 		}
375 		return (ASYNC_DONE);
376 
377 	default:
378 		ar->ar_errno = EOPNOTSUPP;
379 		ar->ar_h_errno = NETDB_INTERNAL;
380 		ar->ar_gai_errno = EAI_SYSTEM;
381 		async_set_state(as, ASR_STATE_HALT);
382                 break;
383 	}
384 	goto next;
385 }
386 
387 /*
388  * Retreive the port number for the service name "servname" and
389  * the protocol "proto".
390  */
391 static int
392 get_port(const char *servname, const char *proto, int numonly)
393 {
394 	struct servent		se;
395 	struct servent_data	sed;
396 	int			port, r;
397 	const char*		e;
398 
399 	if (servname == NULL)
400 		return (0);
401 
402 	e = NULL;
403 	port = strtonum(servname, 0, USHRT_MAX, &e);
404 	if (e == NULL)
405 		return (port);
406 	if (errno == ERANGE)
407 		return (-2); /* invalid */
408 	if (numonly)
409 		return (-2);
410 
411 	memset(&sed, 0, sizeof(sed));
412 	r = getservbyname_r(servname, proto, &se, &sed);
413 	port = ntohs(se.s_port);
414 	endservent_r(&sed);
415 
416 	if (r == -1)
417 		return (-1); /* not found */
418 
419 	return (port);
420 }
421 
422 /*
423  * Iterate over the address families that are to be queried. Use the
424  * list on the async context, unless a specific family was given in hints.
425  */
426 static int
427 iter_family(struct async *as, int first)
428 {
429 	if (first) {
430 		as->as_family_idx = 0;
431 		if (as->as.ai.hints.ai_family != PF_UNSPEC)
432 			return as->as.ai.hints.ai_family;
433 		return AS_FAMILY(as);
434 	}
435 
436 	if (as->as.ai.hints.ai_family != PF_UNSPEC)
437 		return (-1);
438 
439 	as->as_family_idx++;
440 
441 	return AS_FAMILY(as);
442 }
443 
444 /*
445  * Use the sockaddr at "sa" to extend the result list on the "as" context,
446  * with the specified canonical name "cname". This function adds one
447  * entry per protocol/socktype match.
448  */
449 static int
450 add_sockaddr(struct async *as, struct sockaddr *sa, const char *cname)
451 {
452 	struct addrinfo		*ai;
453 	int			 i, port;
454 
455 	for(i = 0; matches[i].family != -1; i++) {
456 		if (matches[i].family != sa->sa_family ||
457 		    !MATCH_SOCKTYPE(as->as.ai.hints.ai_socktype, i) ||
458 		    !MATCH_PROTO(as->as.ai.hints.ai_protocol, i))
459 			continue;
460 
461 		if (matches[i].protocol == IPPROTO_TCP)
462 			port = as->as.ai.port_tcp;
463 		else if (matches[i].protocol == IPPROTO_UDP)
464 			port = as->as.ai.port_udp;
465 		else
466 			port = 0;
467 
468 		ai = calloc(1, sizeof(*ai) + sa->sa_len);
469 		if (ai == NULL)
470 			return (EAI_MEMORY);
471 		ai->ai_family = sa->sa_family;
472 		ai->ai_socktype = matches[i].socktype;
473 		ai->ai_protocol = matches[i].protocol;
474 		ai->ai_addrlen = sa->sa_len;
475 		ai->ai_addr = (void*)(ai + 1);
476 		if (cname &&
477 		    as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) {
478 			if ((ai->ai_canonname = strdup(cname)) == NULL) {
479 				free(ai);
480 				return (EAI_MEMORY);
481 			}
482 		}
483 		memmove(ai->ai_addr, sa, sa->sa_len);
484 		if (sa->sa_family == PF_INET)
485 			((struct sockaddr_in *)ai->ai_addr)->sin_port =
486 			    htons(port);
487 		else if (sa->sa_family == PF_INET6)
488 			((struct sockaddr_in6 *)ai->ai_addr)->sin6_port =
489 			    htons(port);
490 
491 		if (as->as.ai.aifirst == NULL)
492 			as->as.ai.aifirst = ai;
493 		if (as->as.ai.ailast)
494 			as->as.ai.ailast->ai_next = ai;
495 		as->as.ai.ailast = ai;
496 		as->as_count += 1;
497 	}
498 
499 	return (0);
500 }
501