Git init
[external/curl.git] / lib / hostip.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2010, 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  ***************************************************************************/
22
23 #include "setup.h"
24
25 #include <string.h>
26
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
29 #endif
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
32 #endif
33 #ifdef HAVE_NETDB_H
34 #include <netdb.h>
35 #endif
36 #ifdef HAVE_ARPA_INET_H
37 #include <arpa/inet.h>
38 #endif
39 #ifdef HAVE_STDLIB_H
40 #include <stdlib.h>     /* required for free() prototypes */
41 #endif
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>     /* for the close() proto */
44 #endif
45 #ifdef __VMS
46 #include <in.h>
47 #include <inet.h>
48 #include <stdlib.h>
49 #endif
50
51 #ifdef HAVE_SETJMP_H
52 #include <setjmp.h>
53 #endif
54 #ifdef HAVE_SIGNAL_H
55 #include <signal.h>
56 #endif
57
58 #ifdef HAVE_PROCESS_H
59 #include <process.h>
60 #endif
61
62 #include "urldata.h"
63 #include "sendf.h"
64 #include "hostip.h"
65 #include "hash.h"
66 #include "share.h"
67 #include "strerror.h"
68 #include "url.h"
69 #include "inet_ntop.h"
70 #include "warnless.h"
71
72 #define _MPRINTF_REPLACE /* use our functions only */
73 #include <curl/mprintf.h>
74
75 #include "curl_memory.h"
76 /* The last #include file should be: */
77 #include "memdebug.h"
78
79 #if defined(CURLRES_SYNCH) && \
80     defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP)
81 /* alarm-based timeouts can only be used with all the dependencies satisfied */
82 #define USE_ALARM_TIMEOUT
83 #endif
84
85 /*
86  * hostip.c explained
87  * ==================
88  *
89  * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
90  * source file are these:
91  *
92  * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
93  * that. The host may not be able to resolve IPv6, but we don't really have to
94  * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
95  * defined.
96  *
97  * CURLRES_ARES - is defined if libcurl is built to use c-ares for
98  * asynchronous name resolves. This can be Windows or *nix.
99  *
100  * CURLRES_THREADED - is defined if libcurl is built to run under (native)
101  * Windows, and then the name resolve will be done in a new thread, and the
102  * supported API will be the same as for ares-builds.
103  *
104  * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
105  * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
106  * defined.
107  *
108  * The host*.c sources files are split up like this:
109  *
110  * hostip.c   - method-independent resolver functions and utility functions
111  * hostasyn.c - functions for asynchronous name resolves
112  * hostsyn.c  - functions for synchronous name resolves
113  * hostares.c - functions for ares-using name resolves
114  * hostthre.c - functions for threaded name resolves
115  * hostip4.c  - ipv4-specific functions
116  * hostip6.c  - ipv6-specific functions
117  *
118  * The hostip.h is the united header file for all this. It defines the
119  * CURLRES_* defines based on the config*.h and setup.h defines.
120  */
121
122 /* These two symbols are for the global DNS cache */
123 static struct curl_hash hostname_cache;
124 static int host_cache_initialized;
125
126 static void freednsentry(void *freethis);
127
128 /*
129  * Curl_global_host_cache_init() initializes and sets up a global DNS cache.
130  * Global DNS cache is general badness. Do not use. This will be removed in
131  * a future version. Use the share interface instead!
132  *
133  * Returns a struct curl_hash pointer on success, NULL on failure.
134  */
135 struct curl_hash *Curl_global_host_cache_init(void)
136 {
137   int rc = 0;
138   if(!host_cache_initialized) {
139     rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str,
140                         Curl_str_key_compare, freednsentry);
141     if(!rc)
142       host_cache_initialized = 1;
143   }
144   return rc?NULL:&hostname_cache;
145 }
146
147 /*
148  * Destroy and cleanup the global DNS cache
149  */
150 void Curl_global_host_cache_dtor(void)
151 {
152   if(host_cache_initialized) {
153     Curl_hash_clean(&hostname_cache);
154     host_cache_initialized = 0;
155   }
156 }
157
158 /*
159  * Return # of adresses in a Curl_addrinfo struct
160  */
161 int Curl_num_addresses(const Curl_addrinfo *addr)
162 {
163   int i = 0;
164   while(addr) {
165     addr = addr->ai_next;
166     i++;
167   }
168   return i;
169 }
170
171 /*
172  * Curl_printable_address() returns a printable version of the 1st address
173  * given in the 'ai' argument. The result will be stored in the buf that is
174  * bufsize bytes big.
175  *
176  * If the conversion fails, it returns NULL.
177  */
178 const char *
179 Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize)
180 {
181   const struct sockaddr_in *sa4;
182   const struct in_addr *ipaddr4;
183 #ifdef ENABLE_IPV6
184   const struct sockaddr_in6 *sa6;
185   const struct in6_addr *ipaddr6;
186 #endif
187
188   switch (ai->ai_family) {
189     case AF_INET:
190       sa4 = (const void *)ai->ai_addr;
191       ipaddr4 = &sa4->sin_addr;
192       return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf,
193                             bufsize);
194 #ifdef ENABLE_IPV6
195     case AF_INET6:
196       sa6 = (const void *)ai->ai_addr;
197       ipaddr6 = &sa6->sin6_addr;
198       return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf,
199                             bufsize);
200 #endif
201     default:
202       break;
203   }
204   return NULL;
205 }
206
207 /*
208  * Return a hostcache id string for the providing host + port, to be used by
209  * the DNS caching.
210  */
211 static char *
212 create_hostcache_id(const char *server, int port)
213 {
214   /* create and return the new allocated entry */
215   return aprintf("%s:%d", server, port);
216 }
217
218 struct hostcache_prune_data {
219   long cache_timeout;
220   time_t now;
221 };
222
223 /*
224  * This function is set as a callback to be called for every entry in the DNS
225  * cache when we want to prune old unused entries.
226  *
227  * Returning non-zero means remove the entry, return 0 to keep it in the
228  * cache.
229  */
230 static int
231 hostcache_timestamp_remove(void *datap, void *hc)
232 {
233   struct hostcache_prune_data *data =
234     (struct hostcache_prune_data *) datap;
235   struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
236
237   return (data->now - c->timestamp >= data->cache_timeout);
238 }
239
240 /*
241  * Prune the DNS cache. This assumes that a lock has already been taken.
242  */
243 static void
244 hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now)
245 {
246   struct hostcache_prune_data user;
247
248   user.cache_timeout = cache_timeout;
249   user.now = now;
250
251   Curl_hash_clean_with_criterium(hostcache,
252                                  (void *) &user,
253                                  hostcache_timestamp_remove);
254 }
255
256 /*
257  * Library-wide function for pruning the DNS cache. This function takes and
258  * returns the appropriate locks.
259  */
260 void Curl_hostcache_prune(struct SessionHandle *data)
261 {
262   time_t now;
263
264   if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
265     /* cache forever means never prune, and NULL hostcache means
266        we can't do it */
267     return;
268
269   if(data->share)
270     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
271
272   time(&now);
273
274   /* Remove outdated and unused entries from the hostcache */
275   hostcache_prune(data->dns.hostcache,
276                   data->set.dns_cache_timeout,
277                   now);
278
279   if(data->share)
280     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
281 }
282
283 /*
284  * Check if the entry should be pruned. Assumes a locked cache.
285  */
286 static int
287 remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
288 {
289   struct hostcache_prune_data user;
290
291   if( !dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
292     /* cache forever means never prune, and NULL hostcache means
293        we can't do it */
294     return 0;
295
296   time(&user.now);
297   user.cache_timeout = data->set.dns_cache_timeout;
298
299   if( !hostcache_timestamp_remove(&user,dns) )
300     return 0;
301
302   Curl_hash_clean_with_criterium(data->dns.hostcache,
303                                  (void *) &user,
304                                  hostcache_timestamp_remove);
305
306   return 1;
307 }
308
309
310 #ifdef HAVE_SIGSETJMP
311 /* Beware this is a global and unique instance. This is used to store the
312    return address that we can jump back to from inside a signal handler. This
313    is not thread-safe stuff. */
314 sigjmp_buf curl_jmpenv;
315 #endif
316
317
318 /*
319  * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
320  *
321  * When calling Curl_resolv() has resulted in a response with a returned
322  * address, we call this function to store the information in the dns
323  * cache etc
324  *
325  * Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
326  */
327 struct Curl_dns_entry *
328 Curl_cache_addr(struct SessionHandle *data,
329                 Curl_addrinfo *addr,
330                 const char *hostname,
331                 int port)
332 {
333   char *entry_id;
334   size_t entry_len;
335   struct Curl_dns_entry *dns;
336   struct Curl_dns_entry *dns2;
337
338   /* Create an entry id, based upon the hostname and port */
339   entry_id = create_hostcache_id(hostname, port);
340   /* If we can't create the entry id, fail */
341   if(!entry_id)
342     return NULL;
343   entry_len = strlen(entry_id);
344
345   /* Create a new cache entry */
346   dns = calloc(1, sizeof(struct Curl_dns_entry));
347   if(!dns) {
348     free(entry_id);
349     return NULL;
350   }
351
352   dns->inuse = 0;   /* init to not used */
353   dns->addr = addr; /* this is the address(es) */
354   time(&dns->timestamp);
355   if(dns->timestamp == 0)
356     dns->timestamp = 1;   /* zero indicates that entry isn't in hash table */
357
358   /* Store the resolved data in our DNS cache. */
359   dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
360                        (void *)dns);
361   if(!dns2) {
362     free(dns);
363     free(entry_id);
364     return NULL;
365   }
366
367   dns = dns2;
368   dns->inuse++;         /* mark entry as in-use */
369
370   /* free the allocated entry_id */
371   free(entry_id);
372
373   return dns;
374 }
375
376 /*
377  * Curl_resolv() is the main name resolve function within libcurl. It resolves
378  * a name and returns a pointer to the entry in the 'entry' argument (if one
379  * is provided). This function might return immediately if we're using asynch
380  * resolves. See the return codes.
381  *
382  * The cache entry we return will get its 'inuse' counter increased when this
383  * function is used. You MUST call Curl_resolv_unlock() later (when you're
384  * done using this struct) to decrease the counter again.
385  *
386  * In debug mode, we specifically test for an interface name "LocalHost"
387  * and resolve "localhost" instead as a means to permit test cases
388  * to connect to a local test server with any host name.
389  *
390  * Return codes:
391  *
392  * CURLRESOLV_ERROR   (-1) = error, no pointer
393  * CURLRESOLV_RESOLVED (0) = OK, pointer provided
394  * CURLRESOLV_PENDING  (1) = waiting for response, no pointer
395  */
396
397 int Curl_resolv(struct connectdata *conn,
398                 const char *hostname,
399                 int port,
400                 struct Curl_dns_entry **entry)
401 {
402   char *entry_id = NULL;
403   struct Curl_dns_entry *dns = NULL;
404   size_t entry_len;
405   struct SessionHandle *data = conn->data;
406   CURLcode result;
407   int rc = CURLRESOLV_ERROR; /* default to failure */
408
409   *entry = NULL;
410
411   /* Create an entry id, based upon the hostname and port */
412   entry_id = create_hostcache_id(hostname, port);
413   /* If we can't create the entry id, fail */
414   if(!entry_id)
415     return rc;
416
417   entry_len = strlen(entry_id);
418
419   if(data->share)
420     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
421
422   /* See if its already in our dns cache */
423   dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
424
425   /* free the allocated entry_id again */
426   free(entry_id);
427
428   /* See whether the returned entry is stale. Done before we release lock */
429   if( remove_entry_if_stale(data, dns) )
430     dns = NULL; /* the memory deallocation is being handled by the hash */
431
432   if(dns) {
433     dns->inuse++; /* we use it! */
434     rc = CURLRESOLV_RESOLVED;
435   }
436
437   if(data->share)
438     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
439
440   if(!dns) {
441     /* The entry was not in the cache. Resolve it to IP address */
442
443     Curl_addrinfo *addr;
444     int respwait;
445
446     /* Check what IP specifics the app has requested and if we can provide it.
447      * If not, bail out. */
448     if(!Curl_ipvalid(conn))
449       return CURLRESOLV_ERROR;
450
451     /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
452        non-zero value indicating that we need to wait for the response to the
453        resolve call */
454     addr = Curl_getaddrinfo(conn,
455 #ifdef DEBUGBUILD
456                             (data->set.str[STRING_DEVICE]
457                              && !strcmp(data->set.str[STRING_DEVICE],
458                                         "LocalHost"))?"localhost":
459 #endif
460                             hostname, port, &respwait);
461
462     if(!addr) {
463       if(respwait) {
464         /* the response to our resolve call will come asynchronously at
465            a later time, good or bad */
466         /* First, check that we haven't received the info by now */
467         result = Curl_is_resolved(conn, &dns);
468         if(result) /* error detected */
469           return CURLRESOLV_ERROR;
470         if(dns)
471           rc = CURLRESOLV_RESOLVED; /* pointer provided */
472         else
473           rc = CURLRESOLV_PENDING; /* no info yet */
474       }
475     }
476     else {
477       if(data->share)
478         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
479
480       /* we got a response, store it in the cache */
481       dns = Curl_cache_addr(data, addr, hostname, port);
482
483       if(data->share)
484         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
485
486       if(!dns)
487         /* returned failure, bail out nicely */
488         Curl_freeaddrinfo(addr);
489       else
490         rc = CURLRESOLV_RESOLVED;
491     }
492   }
493
494   *entry = dns;
495
496   return rc;
497 }
498
499 #ifdef USE_ALARM_TIMEOUT
500 /*
501  * This signal handler jumps back into the main libcurl code and continues
502  * execution.  This effectively causes the remainder of the application to run
503  * within a signal handler which is nonportable and could lead to problems.
504  */
505 static
506 RETSIGTYPE alarmfunc(int sig)
507 {
508   /* this is for "-ansi -Wall -pedantic" to stop complaining!   (rabe) */
509   (void)sig;
510   siglongjmp(curl_jmpenv, 1);
511   return;
512 }
513 #endif /* USE_ALARM_TIMEOUT */
514
515 /*
516  * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
517  * timeout.  This function might return immediately if we're using asynch
518  * resolves. See the return codes.
519  *
520  * The cache entry we return will get its 'inuse' counter increased when this
521  * function is used. You MUST call Curl_resolv_unlock() later (when you're
522  * done using this struct) to decrease the counter again.
523  *
524  * If built with a synchronous resolver and use of signals is not
525  * disabled by the application, then a nonzero timeout will cause a
526  * timeout after the specified number of milliseconds. Otherwise, timeout
527  * is ignored.
528  *
529  * Return codes:
530  *
531  * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired
532  * CURLRESOLV_ERROR   (-1) = error, no pointer
533  * CURLRESOLV_RESOLVED (0) = OK, pointer provided
534  * CURLRESOLV_PENDING  (1) = waiting for response, no pointer
535  */
536
537 int Curl_resolv_timeout(struct connectdata *conn,
538                         const char *hostname,
539                         int port,
540                         struct Curl_dns_entry **entry,
541                         long timeoutms)
542 {
543 #ifdef USE_ALARM_TIMEOUT
544 #ifdef HAVE_SIGACTION
545   struct sigaction keep_sigact;   /* store the old struct here */
546   volatile bool keep_copysig = FALSE; /* wether old sigact has been saved */
547   struct sigaction sigact;
548 #else
549 #ifdef HAVE_SIGNAL
550   void (*keep_sigact)(int);       /* store the old handler here */
551 #endif /* HAVE_SIGNAL */
552 #endif /* HAVE_SIGACTION */
553   volatile long timeout;
554   volatile unsigned int prev_alarm = 0;
555   struct SessionHandle *data = conn->data;
556 #endif /* USE_ALARM_TIMEOUT */
557   int rc;
558
559   *entry = NULL;
560
561 #ifdef USE_ALARM_TIMEOUT
562   if (data->set.no_signal)
563     /* Ignore the timeout when signals are disabled */
564     timeout = 0;
565   else
566     timeout = timeoutms;
567
568   if(!timeout)
569     /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
570     return Curl_resolv(conn, hostname, port, entry);
571
572   if(timeout < 1000)
573     /* The alarm() function only provides integer second resolution, so if
574        we want to wait less than one second we must bail out already now. */
575     return CURLRESOLV_TIMEDOUT;
576
577   /*************************************************************
578    * Set signal handler to catch SIGALRM
579    * Store the old value to be able to set it back later!
580    *************************************************************/
581 #ifdef HAVE_SIGACTION
582   sigaction(SIGALRM, NULL, &sigact);
583   keep_sigact = sigact;
584   keep_copysig = TRUE; /* yes, we have a copy */
585   sigact.sa_handler = alarmfunc;
586 #ifdef SA_RESTART
587   /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
588   sigact.sa_flags &= ~SA_RESTART;
589 #endif
590   /* now set the new struct */
591   sigaction(SIGALRM, &sigact, NULL);
592 #else /* HAVE_SIGACTION */
593   /* no sigaction(), revert to the much lamer signal() */
594 #ifdef HAVE_SIGNAL
595   keep_sigact = signal(SIGALRM, alarmfunc);
596 #endif
597 #endif /* HAVE_SIGACTION */
598
599   /* alarm() makes a signal get sent when the timeout fires off, and that
600      will abort system calls */
601   prev_alarm = alarm(curlx_sltoui(timeout/1000L));
602
603   /* This allows us to time-out from the name resolver, as the timeout
604      will generate a signal and we will siglongjmp() from that here.
605      This technique has problems (see alarmfunc).
606      This should be the last thing we do before calling Curl_resolv(),
607      as otherwise we'd have to worry about variables that get modified
608      before we invoke Curl_resolv() (and thus use "volatile"). */
609   if(sigsetjmp(curl_jmpenv, 1)) {
610     /* this is coming from a siglongjmp() after an alarm signal */
611     failf(data, "name lookup timed out");
612     rc = CURLRESOLV_ERROR;
613     goto clean_up;
614   }
615
616 #else
617 #ifndef CURLRES_ASYNCH
618   if(timeoutms)
619     infof(conn->data, "timeout on name lookup is not supported\n");
620 #else
621   (void)timeoutms; /* timeoutms not used with an async resolver */
622 #endif
623 #endif /* USE_ALARM_TIMEOUT */
624
625   /* Perform the actual name resolution. This might be interrupted by an
626    * alarm if it takes too long.
627    */
628   rc = Curl_resolv(conn, hostname, port, entry);
629
630 #ifdef USE_ALARM_TIMEOUT
631 clean_up:
632
633   if(!prev_alarm)
634     /* deactivate a possibly active alarm before uninstalling the handler */
635     alarm(0);
636
637 #ifdef HAVE_SIGACTION
638   if(keep_copysig) {
639     /* we got a struct as it looked before, now put that one back nice
640        and clean */
641     sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
642   }
643 #else
644 #ifdef HAVE_SIGNAL
645   /* restore the previous SIGALRM handler */
646   signal(SIGALRM, keep_sigact);
647 #endif
648 #endif /* HAVE_SIGACTION */
649
650   /* switch back the alarm() to either zero or to what it was before minus
651      the time we spent until now! */
652   if(prev_alarm) {
653     /* there was an alarm() set before us, now put it back */
654     unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created);
655
656     /* the alarm period is counted in even number of seconds */
657     unsigned long alarm_set = prev_alarm - elapsed_ms/1000;
658
659     if(!alarm_set ||
660        ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
661       /* if the alarm time-left reached zero or turned "negative" (counted
662          with unsigned values), we should fire off a SIGALRM here, but we
663          won't, and zero would be to switch it off so we never set it to
664          less than 1! */
665       alarm(1);
666       rc = CURLRESOLV_TIMEDOUT;
667       failf(data, "Previous alarm fired off!");
668     }
669     else
670       alarm((unsigned int)alarm_set);
671   }
672 #endif /* USE_ALARM_TIMEOUT */
673
674   return rc;
675 }
676
677 /*
678  * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
679  * made, the struct may be destroyed due to pruning. It is important that only
680  * one unlock is made for each Curl_resolv() call.
681  */
682 void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
683 {
684   DEBUGASSERT(dns && (dns->inuse>0));
685
686   if(data->share)
687     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
688
689   dns->inuse--;
690   /* only free if nobody is using AND it is not in hostcache (timestamp ==
691      0) */
692   if (dns->inuse == 0 && dns->timestamp == 0) {
693     Curl_freeaddrinfo(dns->addr);
694     free(dns);
695   }
696
697   if(data->share)
698     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
699 }
700
701 /*
702  * File-internal: free a cache dns entry.
703  */
704 static void freednsentry(void *freethis)
705 {
706   struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
707
708   /* mark the entry as not in hostcache */
709   p->timestamp = 0;
710   if (p->inuse == 0) {
711     Curl_freeaddrinfo(p->addr);
712     free(p);
713   }
714 }
715
716 /*
717  * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
718  */
719 struct curl_hash *Curl_mk_dnscache(void)
720 {
721   return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry);
722 }
723
724