1 /* $OpenBSD: getaddrinfo_async.c,v 1.11 2013/03/27 07:40:41 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 18 #include <sys/types.h> 19 #include <sys/uio.h> 20 #include <arpa/nameser.h> 21 #ifdef YP 22 #include <rpc/rpc.h> 23 #include <rpcsvc/yp.h> 24 #include <rpcsvc/ypclnt.h> 25 #include "ypinternal.h" 26 #endif 27 28 #include <err.h> 29 #include <errno.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 #include "asr.h" 35 #include "asr_private.h" 36 37 struct match { 38 int family; 39 int socktype; 40 int protocol; 41 }; 42 43 static int getaddrinfo_async_run(struct async *, struct async_res *); 44 static int get_port(const char *, const char *, int); 45 static int iter_family(struct async *, int); 46 static int addrinfo_add(struct async *, const struct sockaddr *, const char *); 47 static int addrinfo_from_file(struct async *, int, FILE *); 48 static int addrinfo_from_pkt(struct async *, char *, size_t); 49 #ifdef YP 50 static int addrinfo_from_yp(struct async *, int, char *); 51 #endif 52 53 static const struct match matches[] = { 54 { PF_INET, SOCK_DGRAM, IPPROTO_UDP }, 55 { PF_INET, SOCK_STREAM, IPPROTO_TCP }, 56 { PF_INET, SOCK_RAW, 0 }, 57 { PF_INET6, SOCK_DGRAM, IPPROTO_UDP }, 58 { PF_INET6, SOCK_STREAM, IPPROTO_TCP }, 59 { PF_INET6, SOCK_RAW, 0 }, 60 { -1, 0, 0, }, 61 }; 62 63 #define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC) 64 #define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0) 65 /* Do not match SOCK_RAW unless explicitely specified */ 66 #define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \ 67 matches[(b)].socktype != SOCK_RAW)) 68 69 struct async * 70 getaddrinfo_async(const char *hostname, const char *servname, 71 const struct addrinfo *hints, struct asr *asr) 72 { 73 struct asr_ctx *ac; 74 struct async *as; 75 76 ac = asr_use_resolver(asr); 77 if ((as = async_new(ac, ASR_GETADDRINFO)) == NULL) 78 goto abort; /* errno set */ 79 as->as_run = getaddrinfo_async_run; 80 81 if (hostname && (as->as.ai.hostname = strdup(hostname)) == NULL) 82 goto abort; /* errno set */ 83 if (servname && (as->as.ai.servname = strdup(servname)) == NULL) 84 goto abort; /* errno set */ 85 if (hints) 86 memmove(&as->as.ai.hints, hints, sizeof *hints); 87 else { 88 memset(&as->as.ai.hints, 0, sizeof as->as.ai.hints); 89 as->as.ai.hints.ai_family = PF_UNSPEC; 90 } 91 92 asr_ctx_unref(ac); 93 return (as); 94 abort: 95 if (as) 96 async_free(as); 97 asr_ctx_unref(ac); 98 return (NULL); 99 } 100 101 static int 102 getaddrinfo_async_run(struct async *as, struct async_res *ar) 103 { 104 #ifdef YP 105 static char *domain = NULL; 106 char *res; 107 int len; 108 #endif 109 const char *str; 110 struct addrinfo *ai; 111 int i, family, r; 112 FILE *f; 113 union { 114 struct sockaddr sa; 115 struct sockaddr_in sain; 116 struct sockaddr_in6 sain6; 117 } sa; 118 119 next: 120 switch (as->as_state) { 121 122 case ASR_STATE_INIT: 123 124 /* 125 * First, make sure the parameters are valid. 126 */ 127 128 as->as_count = 0; 129 130 if (as->as.ai.hostname == NULL && 131 as->as.ai.servname == NULL) { 132 ar->ar_gai_errno = EAI_NONAME; 133 async_set_state(as, ASR_STATE_HALT); 134 break; 135 } 136 137 ai = &as->as.ai.hints; 138 139 if (ai->ai_addrlen || 140 ai->ai_canonname || 141 ai->ai_addr || 142 ai->ai_next) { 143 ar->ar_gai_errno = EAI_BADHINTS; 144 async_set_state(as, ASR_STATE_HALT); 145 break; 146 } 147 148 if (ai->ai_flags & ~AI_MASK || 149 (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) { 150 ar->ar_gai_errno = EAI_BADFLAGS; 151 async_set_state(as, ASR_STATE_HALT); 152 break; 153 } 154 155 if (ai->ai_family != PF_UNSPEC && 156 ai->ai_family != PF_INET && 157 ai->ai_family != PF_INET6) { 158 ar->ar_gai_errno = EAI_FAMILY; 159 async_set_state(as, ASR_STATE_HALT); 160 break; 161 } 162 163 if (ai->ai_socktype && 164 ai->ai_socktype != SOCK_DGRAM && 165 ai->ai_socktype != SOCK_STREAM && 166 ai->ai_socktype != SOCK_RAW) { 167 ar->ar_gai_errno = EAI_SOCKTYPE; 168 async_set_state(as, ASR_STATE_HALT); 169 break; 170 } 171 172 if (ai->ai_protocol && 173 ai->ai_protocol != IPPROTO_UDP && 174 ai->ai_protocol != IPPROTO_TCP) { 175 ar->ar_gai_errno = EAI_PROTOCOL; 176 async_set_state(as, ASR_STATE_HALT); 177 break; 178 } 179 180 if (ai->ai_socktype == SOCK_RAW && 181 as->as.ai.servname != NULL) { 182 ar->ar_gai_errno = EAI_SERVICE; 183 async_set_state(as, ASR_STATE_HALT); 184 break; 185 } 186 187 /* Make sure there is at least a valid combination */ 188 for (i = 0; matches[i].family != -1; i++) 189 if (MATCH_FAMILY(ai->ai_family, i) && 190 MATCH_SOCKTYPE(ai->ai_socktype, i) && 191 MATCH_PROTO(ai->ai_protocol, i)) 192 break; 193 if (matches[i].family == -1) { 194 ar->ar_gai_errno = EAI_BADHINTS; 195 async_set_state(as, ASR_STATE_HALT); 196 break; 197 } 198 199 if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_UDP) 200 as->as.ai.port_udp = get_port(as->as.ai.servname, "udp", 201 as->as.ai.hints.ai_flags & AI_NUMERICSERV); 202 if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_TCP) 203 as->as.ai.port_tcp = get_port(as->as.ai.servname, "tcp", 204 as->as.ai.hints.ai_flags & AI_NUMERICSERV); 205 if (as->as.ai.port_tcp == -2 || as->as.ai.port_udp == -2 || 206 (as->as.ai.port_tcp == -1 && as->as.ai.port_udp == -1) || 207 (ai->ai_protocol && (as->as.ai.port_udp == -1 || 208 as->as.ai.port_tcp == -1))) { 209 ar->ar_gai_errno = EAI_SERVICE; 210 async_set_state(as, ASR_STATE_HALT); 211 break; 212 } 213 214 ar->ar_gai_errno = 0; 215 216 /* If hostname is NULL, use local address */ 217 if (as->as.ai.hostname == NULL) { 218 for (family = iter_family(as, 1); 219 family != -1; 220 family = iter_family(as, 0)) { 221 /* 222 * We could use statically built sockaddrs for 223 * those, rather than parsing over and over. 224 */ 225 if (family == PF_INET) 226 str = (ai->ai_flags & AI_PASSIVE) ? \ 227 "0.0.0.0" : "127.0.0.1"; 228 else /* PF_INET6 */ 229 str = (ai->ai_flags & AI_PASSIVE) ? \ 230 "::" : "::1"; 231 /* This can't fail */ 232 sockaddr_from_str(&sa.sa, family, str); 233 if ((r = addrinfo_add(as, &sa.sa, NULL))) { 234 ar->ar_gai_errno = r; 235 break; 236 } 237 } 238 if (ar->ar_gai_errno == 0 && as->as_count == 0) { 239 ar->ar_gai_errno = EAI_NODATA; 240 } 241 async_set_state(as, ASR_STATE_HALT); 242 break; 243 } 244 245 /* Try numeric addresses first */ 246 for (family = iter_family(as, 1); 247 family != -1; 248 family = iter_family(as, 0)) { 249 250 if (sockaddr_from_str(&sa.sa, family, 251 as->as.ai.hostname) == -1) 252 continue; 253 254 if ((r = addrinfo_add(as, &sa.sa, NULL))) 255 ar->ar_gai_errno = r; 256 break; 257 } 258 if (ar->ar_gai_errno || as->as_count) { 259 async_set_state(as, ASR_STATE_HALT); 260 break; 261 } 262 263 if (ai->ai_flags & AI_NUMERICHOST) { 264 ar->ar_gai_errno = EAI_FAIL; 265 async_set_state(as, ASR_STATE_HALT); 266 break; 267 } 268 269 async_set_state(as, ASR_STATE_NEXT_DB); 270 break; 271 272 case ASR_STATE_NEXT_DB: 273 if (asr_iter_db(as) == -1) { 274 async_set_state(as, ASR_STATE_NOT_FOUND); 275 break; 276 } 277 as->as_family_idx = 0; 278 async_set_state(as, ASR_STATE_SAME_DB); 279 break; 280 281 case ASR_STATE_NEXT_FAMILY: 282 as->as_family_idx += 1; 283 if (as->as.ai.hints.ai_family != AF_UNSPEC || 284 AS_FAMILY(as) == -1) { 285 /* The family was specified, or we have tried all 286 * families with this DB. 287 */ 288 if (as->as_count) { 289 ar->ar_gai_errno = 0; 290 async_set_state(as, ASR_STATE_HALT); 291 } else 292 async_set_state(as, ASR_STATE_NEXT_DB); 293 break; 294 } 295 async_set_state(as, ASR_STATE_SAME_DB); 296 break; 297 298 case ASR_STATE_SAME_DB: 299 /* query the current DB again */ 300 switch (AS_DB(as)) { 301 case ASR_DB_DNS: 302 family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? 303 AS_FAMILY(as) : as->as.ai.hints.ai_family; 304 if (as->as.ai.fqdn) { 305 as->as.ai.subq = res_query_async_ctx( 306 as->as.ai.fqdn, C_IN, 307 (family == AF_INET6) ? T_AAAA : T_A, NULL, 0, 308 as->as_ctx); 309 } 310 else { 311 as->as.ai.subq = res_search_async_ctx( 312 as->as.ai.hostname, C_IN, 313 (family == AF_INET6) ? T_AAAA : T_A, NULL, 0, 314 as->as_ctx); 315 } 316 if (as->as.ai.subq == NULL) { 317 if (errno == ENOMEM) 318 ar->ar_gai_errno = EAI_MEMORY; 319 else 320 ar->ar_gai_errno = EAI_FAIL; 321 async_set_state(as, ASR_STATE_HALT); 322 break; 323 } 324 async_set_state(as, ASR_STATE_SUBQUERY); 325 break; 326 327 case ASR_DB_FILE: 328 f = fopen(as->as_ctx->ac_hostfile, "r"); 329 if (f == NULL) { 330 async_set_state(as, ASR_STATE_NEXT_DB); 331 break; 332 } 333 family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? 334 AS_FAMILY(as) : as->as.ai.hints.ai_family; 335 336 r = addrinfo_from_file(as, family, f); 337 if (r == -1) { 338 if (errno == ENOMEM) 339 ar->ar_gai_errno = EAI_MEMORY; 340 else 341 ar->ar_gai_errno = EAI_FAIL; 342 async_set_state(as, ASR_STATE_HALT); 343 } else 344 async_set_state(as, ASR_STATE_NEXT_FAMILY); 345 fclose(f); 346 break; 347 348 #ifdef YP 349 case ASR_DB_YP: 350 if (!domain && _yp_check(&domain) == 0) { 351 async_set_state(as, ASR_STATE_NEXT_DB); 352 break; 353 } 354 family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? 355 AS_FAMILY(as) : as->as.ai.hints.ai_family; 356 /* XXX 357 * ipnodes.byname could also contain IPv4 address 358 */ 359 r = yp_match(domain, (family == AF_INET6) ? 360 "ipnodes.byname" : "hosts.byname", 361 as->as.ai.hostname, strlen(as->as.ai.hostname), 362 &res, &len); 363 if (r == 0) { 364 r = addrinfo_from_yp(as, family, res); 365 free(res); 366 if (r == -1) { 367 if (errno == ENOMEM) 368 ar->ar_gai_errno = EAI_MEMORY; 369 else 370 ar->ar_gai_errno = EAI_FAIL; 371 async_set_state(as, ASR_STATE_HALT); 372 break; 373 } 374 } 375 async_set_state(as, ASR_STATE_NEXT_FAMILY); 376 break; 377 #endif 378 default: 379 async_set_state(as, ASR_STATE_NEXT_DB); 380 } 381 break; 382 383 case ASR_STATE_SUBQUERY: 384 if ((r = async_run(as->as.ai.subq, ar)) == ASYNC_COND) 385 return (ASYNC_COND); 386 as->as.ai.subq = NULL; 387 388 if (ar->ar_datalen == -1) { 389 async_set_state(as, ASR_STATE_NEXT_FAMILY); 390 break; 391 } 392 393 r = addrinfo_from_pkt(as, ar->ar_data, ar->ar_datalen); 394 if (r == -1) { 395 if (errno == ENOMEM) 396 ar->ar_gai_errno = EAI_MEMORY; 397 else 398 ar->ar_gai_errno = EAI_FAIL; 399 async_set_state(as, ASR_STATE_HALT); 400 } else 401 async_set_state(as, ASR_STATE_NEXT_FAMILY); 402 free(ar->ar_data); 403 break; 404 405 case ASR_STATE_NOT_FOUND: 406 /* No result found. Maybe we can try again. */ 407 if (as->as.ai.flags & ASYNC_AGAIN) 408 ar->ar_gai_errno = EAI_AGAIN; 409 else 410 ar->ar_gai_errno = EAI_NODATA; 411 async_set_state(as, ASR_STATE_HALT); 412 break; 413 414 case ASR_STATE_HALT: 415 if (ar->ar_gai_errno == 0) { 416 ar->ar_count = as->as_count; 417 ar->ar_addrinfo = as->as.ai.aifirst; 418 as->as.ai.aifirst = NULL; 419 } else { 420 ar->ar_count = 0; 421 ar->ar_addrinfo = NULL; 422 } 423 return (ASYNC_DONE); 424 425 default: 426 ar->ar_errno = EOPNOTSUPP; 427 ar->ar_gai_errno = EAI_SYSTEM; 428 async_set_state(as, ASR_STATE_HALT); 429 break; 430 } 431 goto next; 432 } 433 434 /* 435 * Retreive the port number for the service name "servname" and 436 * the protocol "proto". 437 */ 438 static int 439 get_port(const char *servname, const char *proto, int numonly) 440 { 441 struct servent se; 442 struct servent_data sed; 443 int port, r; 444 const char* e; 445 446 if (servname == NULL) 447 return (0); 448 449 e = NULL; 450 port = strtonum(servname, 0, USHRT_MAX, &e); 451 if (e == NULL) 452 return (port); 453 if (errno == ERANGE) 454 return (-2); /* invalid */ 455 if (numonly) 456 return (-2); 457 458 memset(&sed, 0, sizeof(sed)); 459 r = getservbyname_r(servname, proto, &se, &sed); 460 port = ntohs(se.s_port); 461 endservent_r(&sed); 462 463 if (r == -1) 464 return (-1); /* not found */ 465 466 return (port); 467 } 468 469 /* 470 * Iterate over the address families that are to be queried. Use the 471 * list on the async context, unless a specific family was given in hints. 472 */ 473 static int 474 iter_family(struct async *as, int first) 475 { 476 if (first) { 477 as->as_family_idx = 0; 478 if (as->as.ai.hints.ai_family != PF_UNSPEC) 479 return as->as.ai.hints.ai_family; 480 return AS_FAMILY(as); 481 } 482 483 if (as->as.ai.hints.ai_family != PF_UNSPEC) 484 return (-1); 485 486 as->as_family_idx++; 487 488 return AS_FAMILY(as); 489 } 490 491 /* 492 * Use the sockaddr at "sa" to extend the result list on the "as" context, 493 * with the specified canonical name "cname". This function adds one 494 * entry per protocol/socktype match. 495 */ 496 static int 497 addrinfo_add(struct async *as, const struct sockaddr *sa, const char *cname) 498 { 499 struct addrinfo *ai; 500 int i, port; 501 502 for (i = 0; matches[i].family != -1; i++) { 503 if (matches[i].family != sa->sa_family || 504 !MATCH_SOCKTYPE(as->as.ai.hints.ai_socktype, i) || 505 !MATCH_PROTO(as->as.ai.hints.ai_protocol, i)) 506 continue; 507 508 if (matches[i].protocol == IPPROTO_TCP) 509 port = as->as.ai.port_tcp; 510 else if (matches[i].protocol == IPPROTO_UDP) 511 port = as->as.ai.port_udp; 512 else 513 port = 0; 514 515 /* servname specified, but not defined for this protocol */ 516 if (port == -1) 517 continue; 518 519 ai = calloc(1, sizeof(*ai) + sa->sa_len); 520 if (ai == NULL) 521 return (EAI_MEMORY); 522 ai->ai_family = sa->sa_family; 523 ai->ai_socktype = matches[i].socktype; 524 ai->ai_protocol = matches[i].protocol; 525 ai->ai_addrlen = sa->sa_len; 526 ai->ai_addr = (void*)(ai + 1); 527 if (cname && 528 as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) { 529 if ((ai->ai_canonname = strdup(cname)) == NULL) { 530 free(ai); 531 return (EAI_MEMORY); 532 } 533 } 534 memmove(ai->ai_addr, sa, sa->sa_len); 535 if (sa->sa_family == PF_INET) 536 ((struct sockaddr_in *)ai->ai_addr)->sin_port = 537 htons(port); 538 else if (sa->sa_family == PF_INET6) 539 ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = 540 htons(port); 541 542 if (as->as.ai.aifirst == NULL) 543 as->as.ai.aifirst = ai; 544 if (as->as.ai.ailast) 545 as->as.ai.ailast->ai_next = ai; 546 as->as.ai.ailast = ai; 547 as->as_count += 1; 548 } 549 550 return (0); 551 } 552 553 static int 554 addrinfo_from_file(struct async *as, int family, FILE *f) 555 { 556 char *tokens[MAXTOKEN], *c; 557 int n, i; 558 union { 559 struct sockaddr sa; 560 struct sockaddr_in sain; 561 struct sockaddr_in6 sain6; 562 } u; 563 564 for (;;) { 565 n = asr_parse_namedb_line(f, tokens, MAXTOKEN); 566 if (n == -1) 567 break; /* ignore errors reading the file */ 568 569 for (i = 1; i < n; i++) { 570 if (strcasecmp(as->as.ai.hostname, tokens[i])) 571 continue; 572 if (sockaddr_from_str(&u.sa, family, tokens[0]) == -1) 573 continue; 574 break; 575 } 576 if (i == n) 577 continue; 578 579 if (as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) 580 c = tokens[1]; 581 else 582 c = NULL; 583 584 if (addrinfo_add(as, &u.sa, c)) 585 return (-1); /* errno set */ 586 } 587 return (0); 588 } 589 590 static int 591 addrinfo_from_pkt(struct async *as, char *pkt, size_t pktlen) 592 { 593 struct unpack p; 594 struct header h; 595 struct query q; 596 struct rr rr; 597 int i; 598 union { 599 struct sockaddr sa; 600 struct sockaddr_in sain; 601 struct sockaddr_in6 sain6; 602 } u; 603 char buf[MAXDNAME], *c; 604 605 unpack_init(&p, pkt, pktlen); 606 unpack_header(&p, &h); 607 for (; h.qdcount; h.qdcount--) 608 unpack_query(&p, &q); 609 610 for (i = 0; i < h.ancount; i++) { 611 unpack_rr(&p, &rr); 612 if (rr.rr_type != q.q_type || 613 rr.rr_class != q.q_class) 614 continue; 615 616 if (as->as.ai.fqdn == NULL) { 617 asr_strdname(q.q_dname, buf, sizeof buf); 618 buf[strlen(buf) - 1] = '\0'; 619 as->as.ai.fqdn = strdup(buf); 620 if (as->as.ai.fqdn == NULL) 621 return (-1); /* errno set */ 622 } 623 624 memset(&u, 0, sizeof u); 625 if (rr.rr_type == T_A) { 626 u.sain.sin_len = sizeof u.sain; 627 u.sain.sin_family = AF_INET; 628 u.sain.sin_addr = rr.rr.in_a.addr; 629 u.sain.sin_port = 0; 630 } else if (rr.rr_type == T_AAAA) { 631 u.sain6.sin6_len = sizeof u.sain6; 632 u.sain6.sin6_family = AF_INET6; 633 u.sain6.sin6_addr = rr.rr.in_aaaa.addr6; 634 u.sain6.sin6_port = 0; 635 } else 636 continue; 637 638 if (as->as.ai.hints.ai_flags & AI_CANONNAME) { 639 asr_strdname(rr.rr_dname, buf, sizeof buf); 640 buf[strlen(buf) - 1] = '\0'; 641 c = buf; 642 } else if (as->as.ai.hints.ai_flags & AI_FQDN) 643 c = as->as.ai.fqdn; 644 else 645 c = NULL; 646 647 if (addrinfo_add(as, &u.sa, c)) 648 return (-1); /* errno set */ 649 } 650 return (0); 651 } 652 653 #ifdef YP 654 static int 655 strsplit(char *line, char **tokens, int ntokens) 656 { 657 int ntok; 658 char *cp, **tp; 659 660 for (cp = line, tp = tokens, ntok = 0; 661 ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; ) 662 if (**tp != '\0') { 663 tp++; 664 ntok++; 665 } 666 667 return (ntok); 668 } 669 670 static int 671 addrinfo_from_yp(struct async *as, int family, char *line) 672 { 673 char *next, *tokens[MAXTOKEN], *c; 674 int ntok; 675 union { 676 struct sockaddr sa; 677 struct sockaddr_in sain; 678 struct sockaddr_in6 sain6; 679 } u; 680 681 for (next = line; line; line = next) { 682 if ((next = strchr(line, '\n'))) { 683 *next = '\0'; 684 next += 1; 685 } 686 ntok = strsplit(line, tokens, MAXTOKEN); 687 if (ntok < 2) 688 continue; 689 690 if (sockaddr_from_str(&u.sa, family, tokens[0]) == -1) 691 continue; 692 693 if (as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) 694 c = tokens[1]; 695 else 696 c = NULL; 697 698 if (addrinfo_add(as, &u.sa, c)) 699 return (-1); /* errno set */ 700 } 701 return (0); 702 } 703 #endif 704