ntoa() and inet_ntoa_r() no longer used
[platform/upstream/curl.git] / lib / hostip.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at http://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * $Id$
22  ***************************************************************************/
23
24 #include "setup.h"
25
26 #include <string.h>
27
28 #ifdef NEED_MALLOC_H
29 #include <malloc.h>
30 #endif
31 #ifdef HAVE_SYS_SOCKET_H
32 #include <sys/socket.h>
33 #endif
34 #ifdef HAVE_NETINET_IN_H
35 #include <netinet/in.h>
36 #endif
37 #ifdef HAVE_NETDB_H
38 #include <netdb.h>
39 #endif
40 #ifdef HAVE_ARPA_INET_H
41 #include <arpa/inet.h>
42 #endif
43 #ifdef HAVE_STDLIB_H
44 #include <stdlib.h>     /* required for free() prototypes */
45 #endif
46 #ifdef HAVE_UNISTD_H
47 #include <unistd.h>     /* for the close() proto */
48 #endif
49 #ifdef  VMS
50 #include <in.h>
51 #include <inet.h>
52 #include <stdlib.h>
53 #endif
54
55 #ifdef HAVE_SETJMP_H
56 #include <setjmp.h>
57 #endif
58
59 #ifdef HAVE_PROCESS_H
60 #include <process.h>
61 #endif
62
63 #include "urldata.h"
64 #include "sendf.h"
65 #include "hostip.h"
66 #include "hash.h"
67 #include "share.h"
68 #include "strerror.h"
69 #include "url.h"
70 #include "inet_ntop.h"
71
72 #define _MPRINTF_REPLACE /* use our functions only */
73 #include <curl/mprintf.h>
74
75 #include "memory.h"
76 /* The last #include file should be: */
77 #include "memdebug.h"
78
79 /*
80  * hostip.c explained
81  * ==================
82  *
83  * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
84  * source file are these:
85  *
86  * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
87  * that. The host may not be able to resolve IPv6, but we don't really have to
88  * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
89  * defined.
90  *
91  * CURLRES_ARES - is defined if libcurl is built to use c-ares for
92  * asynchronous name resolves. This can be Windows or *nix.
93  *
94  * CURLRES_THREADED - is defined if libcurl is built to run under (native)
95  * Windows, and then the name resolve will be done in a new thread, and the
96  * supported API will be the same as for ares-builds.
97  *
98  * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
99  * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
100  * defined.
101  *
102  * The host*.c sources files are split up like this:
103  *
104  * hostip.c   - method-independent resolver functions and utility functions
105  * hostasyn.c - functions for asynchronous name resolves
106  * hostsyn.c  - functions for synchronous name resolves
107  * hostares.c - functions for ares-using name resolves
108  * hostthre.c - functions for threaded name resolves
109  * hostip4.c  - ipv4-specific functions
110  * hostip6.c  - ipv6-specific functions
111  *
112  * The hostip.h is the united header file for all this. It defines the
113  * CURLRES_* defines based on the config*.h and setup.h defines.
114  */
115
116 /* These two symbols are for the global DNS cache */
117 static struct curl_hash hostname_cache;
118 static int host_cache_initialized;
119
120 static void freednsentry(void *freethis);
121
122 /*
123  * Curl_global_host_cache_init() initializes and sets up a global DNS cache.
124  * Global DNS cache is general badness. Do not use. This will be removed in
125  * a future version. Use the share interface instead!
126  *
127  * Returns a struct curl_hash pointer on success, NULL on failure.
128  */
129 struct curl_hash *Curl_global_host_cache_init(void)
130 {
131   int rc = 0;
132   if(!host_cache_initialized) {
133     rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str,
134                         Curl_str_key_compare, freednsentry);
135     if(!rc)
136       host_cache_initialized = 1;
137   }
138   return rc?NULL:&hostname_cache;
139 }
140
141 /*
142  * Destroy and cleanup the global DNS cache
143  */
144 void Curl_global_host_cache_dtor(void)
145 {
146   if(host_cache_initialized) {
147     Curl_hash_clean(&hostname_cache);
148     host_cache_initialized = 0;
149   }
150 }
151
152 /*
153  * Return # of adresses in a Curl_addrinfo struct
154  */
155 int Curl_num_addresses(const Curl_addrinfo *addr)
156 {
157   int i;
158   for (i = 0; addr; addr = addr->ai_next, i++)
159     ;  /* empty loop */
160   return i;
161 }
162
163 /*
164  * Curl_printable_address() returns a printable version of the 1st address
165  * given in the 'ip' argument. The result will be stored in the buf that is
166  * bufsize bytes big.
167  *
168  * If the conversion fails, it returns NULL.
169  */
170 const char *Curl_printable_address(const Curl_addrinfo *ip,
171                                    char *buf, size_t bufsize)
172 {
173   const void *ip4 = &((const struct sockaddr_in*)ip->ai_addr)->sin_addr;
174   int af = ip->ai_family;
175 #ifdef CURLRES_IPV6
176   const void *ip6 = &((const struct sockaddr_in6*)ip->ai_addr)->sin6_addr;
177 #else
178   const void *ip6 = NULL;
179 #endif
180
181   return Curl_inet_ntop(af, af == AF_INET ? ip4 : ip6, buf, bufsize);
182 }
183
184 /*
185  * Return a hostcache id string for the providing host + port, to be used by
186  * the DNS caching.
187  */
188 static char *
189 create_hostcache_id(const char *server, int port)
190 {
191   /* create and return the new allocated entry */
192   return aprintf("%s:%d", server, port);
193 }
194
195 struct hostcache_prune_data {
196   long cache_timeout;
197   time_t now;
198 };
199
200 /*
201  * This function is set as a callback to be called for every entry in the DNS
202  * cache when we want to prune old unused entries.
203  *
204  * Returning non-zero means remove the entry, return 0 to keep it in the
205  * cache.
206  */
207 static int
208 hostcache_timestamp_remove(void *datap, void *hc)
209 {
210   struct hostcache_prune_data *data =
211     (struct hostcache_prune_data *) datap;
212   struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
213
214   if((data->now - c->timestamp < data->cache_timeout) ||
215       c->inuse) {
216     /* please don't remove */
217     return 0;
218   }
219
220   /* fine, remove */
221   return 1;
222 }
223
224 /*
225  * Prune the DNS cache. This assumes that a lock has already been taken.
226  */
227 static void
228 hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now)
229 {
230   struct hostcache_prune_data user;
231
232   user.cache_timeout = cache_timeout;
233   user.now = now;
234
235   Curl_hash_clean_with_criterium(hostcache,
236                                  (void *) &user,
237                                  hostcache_timestamp_remove);
238 }
239
240 /*
241  * Library-wide function for pruning the DNS cache. This function takes and
242  * returns the appropriate locks.
243  */
244 void Curl_hostcache_prune(struct SessionHandle *data)
245 {
246   time_t now;
247
248   if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
249     /* cache forever means never prune, and NULL hostcache means
250        we can't do it */
251     return;
252
253   if(data->share)
254     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
255
256   time(&now);
257
258   /* Remove outdated and unused entries from the hostcache */
259   hostcache_prune(data->dns.hostcache,
260                   data->set.dns_cache_timeout,
261                   now);
262
263   if(data->share)
264     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
265 }
266
267 /*
268  * Check if the entry should be pruned. Assumes a locked cache.
269  */
270 static int
271 remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
272 {
273   struct hostcache_prune_data user;
274
275   if( !dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
276     /* cache forever means never prune, and NULL hostcache means
277        we can't do it */
278     return 0;
279
280   time(&user.now);
281   user.cache_timeout = data->set.dns_cache_timeout;
282
283   if( !hostcache_timestamp_remove(&user,dns) )
284     return 0;
285
286   Curl_hash_clean_with_criterium(data->dns.hostcache,
287                                  (void *) &user,
288                                  hostcache_timestamp_remove);
289
290   return 1;
291 }
292
293
294 #ifdef HAVE_SIGSETJMP
295 /* Beware this is a global and unique instance. This is used to store the
296    return address that we can jump back to from inside a signal handler. This
297    is not thread-safe stuff. */
298 sigjmp_buf curl_jmpenv;
299 #endif
300
301
302 /*
303  * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
304  *
305  * When calling Curl_resolv() has resulted in a response with a returned
306  * address, we call this function to store the information in the dns
307  * cache etc
308  *
309  * Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
310  */
311 struct Curl_dns_entry *
312 Curl_cache_addr(struct SessionHandle *data,
313                 Curl_addrinfo *addr,
314                 const char *hostname,
315                 int port)
316 {
317   char *entry_id;
318   size_t entry_len;
319   struct Curl_dns_entry *dns;
320   struct Curl_dns_entry *dns2;
321   time_t now;
322
323   /* Create an entry id, based upon the hostname and port */
324   entry_id = create_hostcache_id(hostname, port);
325   /* If we can't create the entry id, fail */
326   if(!entry_id)
327     return NULL;
328   entry_len = strlen(entry_id);
329
330   /* Create a new cache entry */
331   dns = calloc(sizeof(struct Curl_dns_entry), 1);
332   if(!dns) {
333     free(entry_id);
334     return NULL;
335   }
336
337   dns->inuse = 0;   /* init to not used */
338   dns->addr = addr; /* this is the address(es) */
339
340   /* Store the resolved data in our DNS cache. This function may return a
341      pointer to an existing struct already present in the hash, and it may
342      return the same argument we pass in. Make no assumptions. */
343   dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
344                        (void *)dns);
345   if(!dns2) {
346     /* Major badness, run away. */
347     free(dns);
348     free(entry_id);
349     return NULL;
350   }
351   time(&now);
352   dns = dns2;
353
354   dns->timestamp = now; /* used now */
355   dns->inuse++;         /* mark entry as in-use */
356
357   /* free the allocated entry_id again */
358   free(entry_id);
359
360   return dns;
361 }
362
363 /*
364  * Curl_resolv() is the main name resolve function within libcurl. It resolves
365  * a name and returns a pointer to the entry in the 'entry' argument (if one
366  * is provided). This function might return immediately if we're using asynch
367  * resolves. See the return codes.
368  *
369  * The cache entry we return will get its 'inuse' counter increased when this
370  * function is used. You MUST call Curl_resolv_unlock() later (when you're
371  * done using this struct) to decrease the counter again.
372  *
373  * Return codes:
374  *
375  * CURLRESOLV_ERROR   (-1) = error, no pointer
376  * CURLRESOLV_RESOLVED (0) = OK, pointer provided
377  * CURLRESOLV_PENDING  (1) = waiting for response, no pointer
378  */
379
380 int Curl_resolv(struct connectdata *conn,
381                 const char *hostname,
382                 int port,
383                 struct Curl_dns_entry **entry)
384 {
385   char *entry_id = NULL;
386   struct Curl_dns_entry *dns = NULL;
387   size_t entry_len;
388   struct SessionHandle *data = conn->data;
389   CURLcode result;
390   int rc = CURLRESOLV_ERROR; /* default to failure */
391   *entry = NULL;
392
393 #ifdef HAVE_SIGSETJMP
394   /* this allows us to time-out from the name resolver, as the timeout
395      will generate a signal and we will siglongjmp() from that here */
396   if(!data->set.no_signal) {
397     if(sigsetjmp(curl_jmpenv, 1)) {
398       /* this is coming from a siglongjmp() */
399       failf(data, "name lookup timed out");
400       return rc;
401     }
402   }
403 #endif
404
405   /* Create an entry id, based upon the hostname and port */
406   entry_id = create_hostcache_id(hostname, port);
407   /* If we can't create the entry id, fail */
408   if(!entry_id)
409     return rc;
410
411   entry_len = strlen(entry_id);
412
413   if(data->share)
414     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
415
416   /* See if its already in our dns cache */
417   dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
418
419   /* See whether the returned entry is stale. Done before we release lock */
420   if( remove_entry_if_stale(data, dns) )
421     dns = NULL; /* the memory deallocation is being handled by the hash */
422
423   if(dns) {
424     dns->inuse++; /* we use it! */
425     rc = CURLRESOLV_RESOLVED;
426   }
427
428   if(data->share)
429     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
430
431   /* free the allocated entry_id again */
432   free(entry_id);
433
434   if(!dns) {
435     /* The entry was not in the cache. Resolve it to IP address */
436
437     Curl_addrinfo *addr;
438     int respwait;
439
440     /* Check what IP specifics the app has requested and if we can provide it.
441      * If not, bail out. */
442     if(!Curl_ipvalid(data))
443       return CURLRESOLV_ERROR;
444
445     /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
446        non-zero value indicating that we need to wait for the response to the
447        resolve call */
448     addr = Curl_getaddrinfo(conn, hostname, port, &respwait);
449
450     if(!addr) {
451       if(respwait) {
452         /* the response to our resolve call will come asynchronously at
453            a later time, good or bad */
454         /* First, check that we haven't received the info by now */
455         result = Curl_is_resolved(conn, &dns);
456         if(result) /* error detected */
457           return CURLRESOLV_ERROR;
458         if(dns)
459           rc = CURLRESOLV_RESOLVED; /* pointer provided */
460         else
461           rc = CURLRESOLV_PENDING; /* no info yet */
462       }
463     }
464     else {
465       if(data->share)
466         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
467
468       /* we got a response, store it in the cache */
469       dns = Curl_cache_addr(data, addr, hostname, port);
470
471       if(data->share)
472         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
473
474       if(!dns)
475         /* returned failure, bail out nicely */
476         Curl_freeaddrinfo(addr);
477       else
478         rc = CURLRESOLV_RESOLVED;
479     }
480   }
481
482   *entry = dns;
483
484   return rc;
485 }
486
487 /*
488  * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
489  * made, the struct may be destroyed due to pruning. It is important that only
490  * one unlock is made for each Curl_resolv() call.
491  */
492 void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
493 {
494   DEBUGASSERT(dns && (dns->inuse>0));
495
496   if(data->share)
497     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
498
499   dns->inuse--;
500
501   if(data->share)
502     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
503 }
504
505 /*
506  * File-internal: free a cache dns entry.
507  */
508 static void freednsentry(void *freethis)
509 {
510   struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
511
512   Curl_freeaddrinfo(p->addr);
513
514   free(p);
515 }
516
517 /*
518  * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
519  */
520 struct curl_hash *Curl_mk_dnscache(void)
521 {
522   return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry);
523 }
524
525 #ifdef CURLRES_ADDRINFO_COPY
526
527 /* align on even 64bit boundaries */
528 #define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7)))
529
530 /*
531  * Curl_addrinfo_copy() performs a "deep" copy of a hostent into a buffer and
532  * returns a pointer to the malloc()ed copy. You need to call free() on the
533  * returned buffer when you're done with it.
534  */
535 Curl_addrinfo *Curl_addrinfo_copy(const void *org, int port)
536 {
537   const struct hostent *orig = org;
538
539   return Curl_he2ai(orig, port);
540 }
541 #endif /* CURLRES_ADDRINFO_COPY */
542
543 /***********************************************************************
544  * Only for plain-ipv4 and c-ares builds (NOTE: c-ares builds can be IPv6
545  * enabled)
546  **********************************************************************/
547
548 #if defined(CURLRES_IPV4) || defined(CURLRES_ARES)
549 /*
550  * This is a function for freeing name information in a protocol independent
551  * way.
552  */
553 void Curl_freeaddrinfo(Curl_addrinfo *ai)
554 {
555   Curl_addrinfo *next;
556
557   /* walk over the list and free all entries */
558   while(ai) {
559     next = ai->ai_next;
560     if(ai->ai_canonname)
561       free(ai->ai_canonname);
562     free(ai);
563     ai = next;
564   }
565 }
566
567 struct namebuf {
568   struct hostent hostentry;
569   char *h_addr_list[2];
570   struct in_addr addrentry;
571   char h_name[16]; /* 123.123.123.123 = 15 letters is maximum */
572 };
573
574 /*
575  * Curl_ip2addr() takes a 32bit ipv4 internet address as input parameter
576  * together with a pointer to the string version of the address, and it
577  * returns a Curl_addrinfo chain filled in correctly with information for this
578  * address/host.
579  *
580  * The input parameters ARE NOT checked for validity but they are expected
581  * to have been checked already when this is called.
582  */
583 Curl_addrinfo *Curl_ip2addr(in_addr_t num, const char *hostname, int port)
584 {
585   Curl_addrinfo *ai;
586
587 #if defined(VMS) && \
588     defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64)
589 #pragma pointer_size save
590 #pragma pointer_size short
591 #pragma message disable PTRMISMATCH
592 #endif
593
594   struct hostent *h;
595   struct in_addr *addrentry;
596   struct namebuf buffer;
597   struct namebuf *buf = &buffer;
598
599   h = &buf->hostentry;
600   h->h_addr_list = &buf->h_addr_list[0];
601   addrentry = &buf->addrentry;
602 #ifdef _CRAYC
603   /* On UNICOS, s_addr is a bit field and for some reason assigning to it
604    * doesn't work.  There must be a better fix than this ugly hack.
605    */
606   memcpy(addrentry, &num, SIZEOF_in_addr);
607 #else
608   addrentry->s_addr = num;
609 #endif
610   h->h_addr_list[0] = (char*)addrentry;
611   h->h_addr_list[1] = NULL;
612   h->h_addrtype = AF_INET;
613   h->h_length = sizeof(*addrentry);
614   h->h_name = &buf->h_name[0];
615   h->h_aliases = NULL;
616
617   /* Now store the dotted version of the address */
618   snprintf(h->h_name, 16, "%s", hostname);
619
620 #if defined(VMS) && \
621     defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64)
622 #pragma pointer_size restore
623 #pragma message enable PTRMISMATCH
624 #endif
625
626   ai = Curl_he2ai(h, port);
627
628   return ai;
629 }
630
631 /*
632  * Curl_he2ai() translates from a hostent struct to a Curl_addrinfo struct.
633  * The Curl_addrinfo is meant to work like the addrinfo struct does for IPv6
634  * stacks, but for all hosts and environments.
635  *
636  *   Curl_addrinfo defined in "lib/hostip.h"
637  *
638  *     struct Curl_addrinfo {
639  *       int                   ai_flags;
640  *       int                   ai_family;
641  *       int                   ai_socktype;
642  *       int                   ai_protocol;
643  *       socklen_t             ai_addrlen;   * Follow rfc3493 struct addrinfo *
644  *       char                 *ai_canonname;
645  *       struct sockaddr      *ai_addr;
646  *       struct Curl_addrinfo *ai_next;
647  *     };
648  *
649  *   hostent defined in <netdb.h>
650  *
651  *     struct hostent {
652  *       char    *h_name;
653  *       char    **h_aliases;
654  *       int     h_addrtype;
655  *       int     h_length;
656  *       char    **h_addr_list;
657  *     };
658  *
659  *   for backward compatibility:
660  *
661  *     #define h_addr  h_addr_list[0]
662  */
663
664 Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port)
665 {
666   Curl_addrinfo *ai;
667   Curl_addrinfo *prevai = NULL;
668   Curl_addrinfo *firstai = NULL;
669   struct sockaddr_in *addr;
670 #ifdef CURLRES_IPV6
671   struct sockaddr_in6 *addr6;
672 #endif /* CURLRES_IPV6 */
673   int i;
674   struct in_addr *curr;
675
676   if(!he)
677     /* no input == no output! */
678     return NULL;
679
680   for(i=0; (curr = (struct in_addr *)he->h_addr_list[i]) != NULL; i++) {
681
682     int ss_size;
683 #ifdef CURLRES_IPV6
684     if (he->h_addrtype == AF_INET6)
685       ss_size = sizeof (struct sockaddr_in6);
686     else
687 #endif /* CURLRES_IPV6 */
688       ss_size = sizeof (struct sockaddr_in);
689
690     ai = calloc(1, sizeof(Curl_addrinfo) + ss_size);
691
692     if(!ai)
693       break;
694
695     if(!firstai)
696       /* store the pointer we want to return from this function */
697       firstai = ai;
698
699     if(prevai)
700       /* make the previous entry point to this */
701       prevai->ai_next = ai;
702
703     ai->ai_family = he->h_addrtype;
704
705     /* we return all names as STREAM, so when using this address for TFTP
706        the type must be ignored and conn->socktype be used instead! */
707     ai->ai_socktype = SOCK_STREAM;
708
709     ai->ai_addrlen = ss_size;
710     /* make the ai_addr point to the address immediately following this struct
711        and use that area to store the address */
712     ai->ai_addr = (struct sockaddr *) ((char*)ai + sizeof(Curl_addrinfo));
713
714     /* need to free this eventually */
715     ai->ai_canonname = strdup(he->h_name);
716
717     /* leave the rest of the struct filled with zero */
718
719     switch (ai->ai_family) {
720     case AF_INET:
721       addr = (struct sockaddr_in *)ai->ai_addr; /* storage area for this info */
722
723       memcpy((char *)&(addr->sin_addr), curr, sizeof(struct in_addr));
724       addr->sin_family = (unsigned short)(he->h_addrtype);
725       addr->sin_port = htons((unsigned short)port);
726       break;
727
728 #ifdef CURLRES_IPV6
729     case AF_INET6:
730       addr6 = (struct sockaddr_in6 *)ai->ai_addr; /* storage area for this info */
731
732       memcpy((char *)&(addr6->sin6_addr), curr, sizeof(struct in6_addr));
733       addr6->sin6_family = (unsigned short)(he->h_addrtype);
734       addr6->sin6_port = htons((unsigned short)port);
735       break;
736 #endif /* CURLRES_IPV6 */
737     }
738
739     prevai = ai;
740   }
741   return firstai;
742 }
743
744 #endif /* CURLRES_IPV4 || CURLRES_ARES */