asyn-thread.c
Go to the documentation of this file.
1 /***************************************************************************
2  * _ _ ____ _
3  * Project ___| | | | _ \| |
4  * / __| | | | |_) | |
5  * | (__| |_| | _ <| |___
6  * \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2017, 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 https://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 "curl_setup.h"
24 
25 /***********************************************************************
26  * Only for threaded name resolves builds
27  **********************************************************************/
28 #ifdef CURLRES_THREADED
29 
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 __VMS
40 #include <in.h>
41 #include <inet.h>
42 #endif
43 
44 #if defined(USE_THREADS_POSIX)
45 # ifdef HAVE_PTHREAD_H
46 # include <pthread.h>
47 # endif
48 #elif defined(USE_THREADS_WIN32)
49 # ifdef HAVE_PROCESS_H
50 # include <process.h>
51 # endif
52 #endif
53 
54 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
55 #undef in_addr_t
56 #define in_addr_t unsigned long
57 #endif
58 
59 #ifdef HAVE_GETADDRINFO
60 # define RESOLVER_ENOMEM EAI_MEMORY
61 #else
62 # define RESOLVER_ENOMEM ENOMEM
63 #endif
64 
65 #include "urldata.h"
66 #include "sendf.h"
67 #include "hostip.h"
68 #include "hash.h"
69 #include "share.h"
70 #include "strerror.h"
71 #include "url.h"
72 #include "multiif.h"
73 #include "inet_pton.h"
74 #include "inet_ntop.h"
75 #include "curl_threads.h"
76 #include "connect.h"
77 /* The last 3 #include files should be in this order */
78 #include "curl_printf.h"
79 #include "curl_memory.h"
80 #include "memdebug.h"
81 
82 /*
83  * Curl_resolver_global_init()
84  * Called from curl_global_init() to initialize global resolver environment.
85  * Does nothing here.
86  */
88 {
89  return CURLE_OK;
90 }
91 
92 /*
93  * Curl_resolver_global_cleanup()
94  * Called from curl_global_cleanup() to destroy global resolver environment.
95  * Does nothing here.
96  */
98 {
99 }
100 
101 /*
102  * Curl_resolver_init()
103  * Called from curl_easy_init() -> Curl_open() to initialize resolver
104  * URL-state specific environment ('resolver' member of the UrlState
105  * structure). Does nothing here.
106  */
107 CURLcode Curl_resolver_init(void **resolver)
108 {
109  (void)resolver;
110  return CURLE_OK;
111 }
112 
113 /*
114  * Curl_resolver_cleanup()
115  * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver
116  * URL-state specific environment ('resolver' member of the UrlState
117  * structure). Does nothing here.
118  */
119 void Curl_resolver_cleanup(void *resolver)
120 {
121  (void)resolver;
122 }
123 
124 /*
125  * Curl_resolver_duphandle()
126  * Called from curl_easy_duphandle() to duplicate resolver URL state-specific
127  * environment ('resolver' member of the UrlState structure). Does nothing
128  * here.
129  */
130 int Curl_resolver_duphandle(void **to, void *from)
131 {
132  (void)to;
133  (void)from;
134  return CURLE_OK;
135 }
136 
137 static void destroy_async_data(struct Curl_async *);
138 
139 /*
140  * Cancel all possibly still on-going resolves for this connection.
141  */
142 void Curl_resolver_cancel(struct connectdata *conn)
143 {
144  destroy_async_data(&conn->async);
145 }
146 
147 /* This function is used to init a threaded resolve */
148 static bool init_resolve_thread(struct connectdata *conn,
149  const char *hostname, int port,
150  const struct addrinfo *hints);
151 
152 
153 /* Data for synchronization between resolver thread and its parent */
154 struct thread_sync_data {
155  curl_mutex_t * mtx;
156  int done;
157 
158  char *hostname; /* hostname to resolve, Curl_async.hostname
159  duplicate */
160  int port;
161  int sock_error;
163 #ifdef HAVE_GETADDRINFO
164  struct addrinfo hints;
165 #endif
166  struct thread_data *td; /* for thread-self cleanup */
167 };
168 
169 struct thread_data {
170  curl_thread_t thread_hnd;
171  unsigned int poll_interval;
172  time_t interval_end;
173  struct thread_sync_data tsd;
174 };
175 
176 static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn)
177 {
178  return &(((struct thread_data *)conn->async.os_specific)->tsd);
179 }
180 
181 #define CONN_THREAD_SYNC_DATA(conn) &(((conn)->async.os_specific)->tsd);
182 
183 /* Destroy resolver thread synchronization data */
184 static
185 void destroy_thread_sync_data(struct thread_sync_data * tsd)
186 {
187  if(tsd->mtx) {
188  Curl_mutex_destroy(tsd->mtx);
189  free(tsd->mtx);
190  }
191 
192  free(tsd->hostname);
193 
194  if(tsd->res)
195  Curl_freeaddrinfo(tsd->res);
196 
197  memset(tsd, 0, sizeof(*tsd));
198 }
199 
200 /* Initialize resolver thread synchronization data */
201 static
202 int init_thread_sync_data(struct thread_data * td,
203  const char *hostname,
204  int port,
205  const struct addrinfo *hints)
206 {
207  struct thread_sync_data *tsd = &td->tsd;
208 
209  memset(tsd, 0, sizeof(*tsd));
210 
211  tsd->td = td;
212  tsd->port = port;
213  /* Treat the request as done until the thread actually starts so any early
214  * cleanup gets done properly.
215  */
216  tsd->done = 1;
217 #ifdef HAVE_GETADDRINFO
218  DEBUGASSERT(hints);
219  tsd->hints = *hints;
220 #else
221  (void) hints;
222 #endif
223 
224  tsd->mtx = malloc(sizeof(curl_mutex_t));
225  if(tsd->mtx == NULL)
226  goto err_exit;
227 
228  Curl_mutex_init(tsd->mtx);
229 
230  tsd->sock_error = CURL_ASYNC_SUCCESS;
231 
232  /* Copying hostname string because original can be destroyed by parent
233  * thread during gethostbyname execution.
234  */
235  tsd->hostname = strdup(hostname);
236  if(!tsd->hostname)
237  goto err_exit;
238 
239  return 1;
240 
241  err_exit:
242  /* Memory allocation failed */
243  destroy_thread_sync_data(tsd);
244  return 0;
245 }
246 
247 static int getaddrinfo_complete(struct connectdata *conn)
248 {
249  struct thread_sync_data *tsd = conn_thread_sync_data(conn);
250  int rc;
251 
252  rc = Curl_addrinfo_callback(conn, tsd->sock_error, tsd->res);
253  /* The tsd->res structure has been copied to async.dns and perhaps the DNS
254  cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it.
255  */
256  tsd->res = NULL;
257 
258  return rc;
259 }
260 
261 
262 #ifdef HAVE_GETADDRINFO
263 
264 /*
265  * getaddrinfo_thread() resolves a name and then exits.
266  *
267  * For builds without ARES, but with ENABLE_IPV6, create a resolver thread
268  * and wait on it.
269  */
270 static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg)
271 {
272  struct thread_sync_data *tsd = (struct thread_sync_data*)arg;
273  struct thread_data *td = tsd->td;
274  char service[12];
275  int rc;
276 
277  snprintf(service, sizeof(service), "%d", tsd->port);
278 
279  rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res);
280 
281  if(rc != 0) {
282  tsd->sock_error = SOCKERRNO?SOCKERRNO:rc;
283  if(tsd->sock_error == 0)
284  tsd->sock_error = RESOLVER_ENOMEM;
285  }
286  else {
287  Curl_addrinfo_set_port(tsd->res, tsd->port);
288  }
289 
290  Curl_mutex_acquire(tsd->mtx);
291  if(tsd->done) {
292  /* too late, gotta clean up the mess */
293  Curl_mutex_release(tsd->mtx);
294  destroy_thread_sync_data(tsd);
295  free(td);
296  }
297  else {
298  tsd->done = 1;
299  Curl_mutex_release(tsd->mtx);
300  }
301 
302  return 0;
303 }
304 
305 #else /* HAVE_GETADDRINFO */
306 
307 /*
308  * gethostbyname_thread() resolves a name and then exits.
309  */
310 static unsigned int CURL_STDCALL gethostbyname_thread(void *arg)
311 {
312  struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
313  struct thread_data *td = tsd->td;
314 
315  tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
316 
317  if(!tsd->res) {
318  tsd->sock_error = SOCKERRNO;
319  if(tsd->sock_error == 0)
320  tsd->sock_error = RESOLVER_ENOMEM;
321  }
322 
323  Curl_mutex_acquire(tsd->mtx);
324  if(tsd->done) {
325  /* too late, gotta clean up the mess */
326  Curl_mutex_release(tsd->mtx);
327  destroy_thread_sync_data(tsd);
328  free(td);
329  }
330  else {
331  tsd->done = 1;
332  Curl_mutex_release(tsd->mtx);
333  }
334 
335  return 0;
336 }
337 
338 #endif /* HAVE_GETADDRINFO */
339 
340 /*
341  * destroy_async_data() cleans up async resolver data and thread handle.
342  */
343 static void destroy_async_data(struct Curl_async *async)
344 {
345  if(async->os_specific) {
346  struct thread_data *td = (struct thread_data*) async->os_specific;
347  int done;
348 
349  /*
350  * if the thread is still blocking in the resolve syscall, detach it and
351  * let the thread do the cleanup...
352  */
353  Curl_mutex_acquire(td->tsd.mtx);
354  done = td->tsd.done;
355  td->tsd.done = 1;
356  Curl_mutex_release(td->tsd.mtx);
357 
358  if(!done) {
359  Curl_thread_destroy(td->thread_hnd);
360  }
361  else {
362  if(td->thread_hnd != curl_thread_t_null)
363  Curl_thread_join(&td->thread_hnd);
364 
365  destroy_thread_sync_data(&td->tsd);
366 
367  free(async->os_specific);
368  }
369  }
370  async->os_specific = NULL;
371 
372  free(async->hostname);
373  async->hostname = NULL;
374 }
375 
376 /*
377  * init_resolve_thread() starts a new thread that performs the actual
378  * resolve. This function returns before the resolve is done.
379  *
380  * Returns FALSE in case of failure, otherwise TRUE.
381  */
382 static bool init_resolve_thread(struct connectdata *conn,
383  const char *hostname, int port,
384  const struct addrinfo *hints)
385 {
386  struct thread_data *td = calloc(1, sizeof(struct thread_data));
387  int err = ENOMEM;
388 
389  conn->async.os_specific = (void *)td;
390  if(!td)
391  goto errno_exit;
392 
393  conn->async.port = port;
394  conn->async.done = FALSE;
395  conn->async.status = 0;
396  conn->async.dns = NULL;
397  td->thread_hnd = curl_thread_t_null;
398 
399  if(!init_thread_sync_data(td, hostname, port, hints)) {
400  conn->async.os_specific = NULL;
401  free(td);
402  goto errno_exit;
403  }
404 
405  free(conn->async.hostname);
406  conn->async.hostname = strdup(hostname);
407  if(!conn->async.hostname)
408  goto err_exit;
409 
410  /* The thread will set this to 1 when complete. */
411  td->tsd.done = 0;
412 
413 #ifdef HAVE_GETADDRINFO
414  td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
415 #else
416  td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd);
417 #endif
418 
419  if(!td->thread_hnd) {
420  /* The thread never started, so mark it as done here for proper cleanup. */
421  td->tsd.done = 1;
422  err = errno;
423  goto err_exit;
424  }
425 
426  return TRUE;
427 
428  err_exit:
429  destroy_async_data(&conn->async);
430 
431  errno_exit:
432  errno = err;
433  return FALSE;
434 }
435 
436 /*
437  * resolver_error() calls failf() with the appropriate message after a resolve
438  * error
439  */
440 
441 static CURLcode resolver_error(struct connectdata *conn)
442 {
443  const char *host_or_proxy;
445 
446  if(conn->bits.httpproxy) {
447  host_or_proxy = "proxy";
449  }
450  else {
451  host_or_proxy = "host";
453  }
454 
455  failf(conn->data, "Could not resolve %s: %s", host_or_proxy,
456  conn->async.hostname);
457 
458  return result;
459 }
460 
461 /*
462  * Curl_resolver_wait_resolv()
463  *
464  * waits for a resolve to finish. This function should be avoided since using
465  * this risk getting the multi interface to "hang".
466  *
467  * If 'entry' is non-NULL, make it point to the resolved dns entry
468  *
469  * This is the version for resolves-in-a-thread.
470  */
472  struct Curl_dns_entry **entry)
473 {
474  struct thread_data *td = (struct thread_data*) conn->async.os_specific;
475  CURLcode result = CURLE_OK;
476 
477  DEBUGASSERT(conn && td);
478 
479  /* wait for the thread to resolve the name */
480  if(Curl_thread_join(&td->thread_hnd))
481  result = getaddrinfo_complete(conn);
482  else
483  DEBUGASSERT(0);
484 
485  conn->async.done = TRUE;
486 
487  if(entry)
488  *entry = conn->async.dns;
489 
490  if(!conn->async.dns)
491  /* a name was not resolved, report error */
492  result = resolver_error(conn);
493 
494  destroy_async_data(&conn->async);
495 
496  if(!conn->async.dns)
497  connclose(conn, "asynch resolve failed");
498 
499  return result;
500 }
501 
502 /*
503  * Curl_resolver_is_resolved() is called repeatedly to check if a previous
504  * name resolve request has completed. It should also make sure to time-out if
505  * the operation seems to take too long.
506  */
508  struct Curl_dns_entry **entry)
509 {
510  struct Curl_easy *data = conn->data;
511  struct thread_data *td = (struct thread_data*) conn->async.os_specific;
512  int done = 0;
513 
514  *entry = NULL;
515 
516  if(!td) {
517  DEBUGASSERT(td);
519  }
520 
521  Curl_mutex_acquire(td->tsd.mtx);
522  done = td->tsd.done;
523  Curl_mutex_release(td->tsd.mtx);
524 
525  if(done) {
526  getaddrinfo_complete(conn);
527 
528  if(!conn->async.dns) {
529  CURLcode result = resolver_error(conn);
530  destroy_async_data(&conn->async);
531  return result;
532  }
533  destroy_async_data(&conn->async);
534  *entry = conn->async.dns;
535  }
536  else {
537  /* poll for name lookup done with exponential backoff up to 250ms */
538  time_t elapsed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
539  if(elapsed < 0)
540  elapsed = 0;
541 
542  if(td->poll_interval == 0)
543  /* Start at 1ms poll interval */
544  td->poll_interval = 1;
545  else if(elapsed >= td->interval_end)
546  /* Back-off exponentially if last interval expired */
547  td->poll_interval *= 2;
548 
549  if(td->poll_interval > 250)
550  td->poll_interval = 250;
551 
552  td->interval_end = elapsed + td->poll_interval;
553  Curl_expire(conn->data, td->poll_interval, EXPIRE_ASYNC_NAME);
554  }
555 
556  return CURLE_OK;
557 }
558 
559 int Curl_resolver_getsock(struct connectdata *conn,
560  curl_socket_t *socks,
561  int numsocks)
562 {
563  (void)conn;
564  (void)socks;
565  (void)numsocks;
566  return 0;
567 }
568 
569 #ifndef HAVE_GETADDRINFO
570 /*
571  * Curl_getaddrinfo() - for platforms without getaddrinfo
572  */
574  const char *hostname,
575  int port,
576  int *waitp)
577 {
578  struct in_addr in;
579 
580  *waitp = 0; /* default to synchronous response */
581 
582  if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
583  /* This is a dotted IP address 123.123.123.123-style */
584  return Curl_ip2addr(AF_INET, &in, hostname, port);
585 
586  /* fire up a new resolver thread! */
587  if(init_resolve_thread(conn, hostname, port, NULL)) {
588  *waitp = 1; /* expect asynchronous response */
589  return NULL;
590  }
591 
592  /* fall-back to blocking version */
593  return Curl_ipv4_resolve_r(hostname, port);
594 }
595 
596 #else /* !HAVE_GETADDRINFO */
597 
598 /*
599  * Curl_resolver_getaddrinfo() - for getaddrinfo
600  */
602  const char *hostname,
603  int port,
604  int *waitp)
605 {
606  struct addrinfo hints;
608  int error;
609  char sbuf[12];
610  int pf = PF_INET;
611 
612  *waitp = 0; /* default to synchronous response */
613 
614 #ifndef USE_RESOLVE_ON_IPS
615  {
616  struct in_addr in;
617  /* First check if this is an IPv4 address string */
618  if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
619  /* This is a dotted IP address 123.123.123.123-style */
620  return Curl_ip2addr(AF_INET, &in, hostname, port);
621  }
622 #ifdef CURLRES_IPV6
623  {
624  struct in6_addr in6;
625  /* check if this is an IPv6 address string */
626  if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0)
627  /* This is an IPv6 address literal */
628  return Curl_ip2addr(AF_INET6, &in6, hostname, port);
629  }
630 #endif /* CURLRES_IPV6 */
631 #endif /* !USE_RESOLVE_ON_IPS */
632 
633 #ifdef CURLRES_IPV6
634  /*
635  * Check if a limited name resolve has been requested.
636  */
637  switch(conn->ip_version) {
638  case CURL_IPRESOLVE_V4:
639  pf = PF_INET;
640  break;
641  case CURL_IPRESOLVE_V6:
642  pf = PF_INET6;
643  break;
644  default:
645  pf = PF_UNSPEC;
646  break;
647  }
648 
649  if((pf != PF_INET) && !Curl_ipv6works())
650  /* The stack seems to be a non-IPv6 one */
651  pf = PF_INET;
652 #endif /* CURLRES_IPV6 */
653 
654  memset(&hints, 0, sizeof(hints));
655  hints.ai_family = pf;
656  hints.ai_socktype = conn->socktype;
657 
658  snprintf(sbuf, sizeof(sbuf), "%d", port);
659 
660  /* fire up a new resolver thread! */
661  if(init_resolve_thread(conn, hostname, port, &hints)) {
662  *waitp = 1; /* expect asynchronous response */
663  return NULL;
664  }
665 
666  /* fall-back to blocking version */
667  infof(conn->data, "init_resolve_thread() failed for %s; %s\n",
668  hostname, Curl_strerror(conn, errno));
669 
670  error = Curl_getaddrinfo_ex(hostname, sbuf, &hints, &res);
671  if(error) {
672  infof(conn->data, "getaddrinfo() failed for %s:%d; %s\n",
673  hostname, port, Curl_strerror(conn, SOCKERRNO));
674  return NULL;
675  }
676  else {
677  Curl_addrinfo_set_port(res, port);
678  }
679 
680  return res;
681 }
682 
683 #endif /* !HAVE_GETADDRINFO */
684 
686  char *servers)
687 {
688  (void)data;
689  (void)servers;
690  return CURLE_NOT_BUILT_IN;
691 
692 }
693 
695  const char *interf)
696 {
697  (void)data;
698  (void)interf;
699  return CURLE_NOT_BUILT_IN;
700 }
701 
703  const char *local_ip4)
704 {
705  (void)data;
706  (void)local_ip4;
707  return CURLE_NOT_BUILT_IN;
708 }
709 
711  const char *local_ip6)
712 {
713  (void)data;
714  (void)local_ip6;
715  return CURLE_NOT_BUILT_IN;
716 }
717 
718 #endif /* CURLRES_THREADED */
#define free(ptr)
Definition: curl_memory.h:130
#define CURL_IPRESOLVE_V6
Definition: curl.h:1856
struct ConnectBits bits
Definition: urldata.h:893
#define connclose(x, y)
Definition: connect.h:141
CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, const char *local_ip4)
Definition: hostsyn.c:87
int Curl_resolver_getsock(struct connectdata *conn, curl_socket_t *sock, int numsocks)
Curl_addrinfo * Curl_ipv4_resolve_r(const char *hostname, int port)
Definition: hostip4.c:122
#define failf
Definition: sendf.h:48
#define CURL_IPRESOLVE_V4
Definition: curl.h:1855
#define strdup(ptr)
Definition: curl_memory.h:122
#define SOCKERRNO
Definition: hostip.h:66
#define DEBUGASSERT(x)
CURLcode
Definition: curl.h:454
CURLcode Curl_resolver_init(void **resolver)
Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port)
void Curl_freeaddrinfo(Curl_addrinfo *cahead)
Definition: curl_addrinfo.c:78
static int res
#define malloc(size)
Definition: curl_memory.h:124
UNITTEST_START int result
Definition: unit1304.c:49
void Curl_resolver_cancel(struct connectdata *conn)
static srvr_sockaddr_union_t from
Definition: tftpd.c:197
#define CURL_ASYNC_SUCCESS
Definition: hostip.h:49
Curl_addrinfo * Curl_resolver_getaddrinfo(struct connectdata *conn, const char *hostname, int port, int *waitp)
CURLcode Curl_addrinfo_callback(struct connectdata *conn, int status, Curl_addrinfo *ai)
#define FALSE
CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, const char *local_ip6)
Definition: hostsyn.c:99
UNITTEST_START int rc
Definition: unit1301.c:31
const char * Curl_strerror(struct connectdata *conn, int err)
Definition: strerror.c:646
CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers)
Definition: hostsyn.c:62
bool httpproxy
Definition: urldata.h:383
#define Curl_tvnow()
Definition: timeval.h:57
int socktype
Definition: urldata.h:831
struct Progress progress
Definition: urldata.h:1768
void Curl_resolver_cleanup(void *resolver)
Definition: curl.h:455
#define Curl_resolver_global_init()
Definition: asyn.h:155
int Curl_inet_pton(int af, const char *src, void *dst)
Definition: inet_pton.c:66
static unsigned short port
Definition: sockfilt.c:137
#define Curl_ipv6works()
Definition: hostip.h:98
#define ENOMEM
long ip_version
Definition: urldata.h:907
struct curltime t_startsingle
Definition: urldata.h:1119
CURLcode Curl_resolver_is_resolved(struct connectdata *conn, struct Curl_dns_entry **dns)
#define infof
Definition: sendf.h:44
#define Curl_tvdiff(x, y)
Definition: timeval.h:58
int Curl_resolver_duphandle(void **to, void *from)
long port
Definition: urldata.h:841
#define Curl_resolver_global_cleanup()
Definition: asyn.h:156
#define snprintf
Definition: curl_printf.h:42
CURLcode Curl_resolver_wait_resolv(struct connectdata *conn, struct Curl_dns_entry **dnsentry)
void Curl_expire(struct Curl_easy *data, time_t milli, expire_id id)
Definition: multi.c:2930
#define TRUE
int curl_socket_t
Definition: curl.h:130
Definition: debug.c:29
CURLcode Curl_set_dns_interface(struct Curl_easy *data, const char *interf)
Definition: hostsyn.c:75
#define calloc(nbelem, size)
Definition: curl_memory.h:126
struct Curl_easy * data
Definition: urldata.h:791


rc_tagdetect_client
Author(s): Monika Florek-Jasinska , Raphael Schaller
autogenerated on Sat Feb 13 2021 03:42:08