Newer
Older
/***************************************************************************
Daniel Stenberg
committed
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
Daniel Stenberg
committed
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
#include "setup.h"
#include <string.h>
#ifdef NEED_MALLOC_H
#include <malloc.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h> /* required for free() prototypes */
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for the close() proto */
#endif
#include <in.h>
#include <inet.h>
#include <stdlib.h>
#endif
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#include <process.h>
#endif
#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#include "urldata.h"
#include "sendf.h"
#include "hostip.h"
#include "hash.h"
#include "share.h"
#include "strerror.h"
#include "url.h"
Daniel Stenberg
committed
#include "multiif.h"
#include "connect.h"
#include "select.h"
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
#include "inet_ntoa_r.h"
#endif
#include "memory.h"
/* The last #include file should be: */
#include "memdebug.h"
/***********************************************************************
* Only for ares-enabled builds
**********************************************************************/
#ifdef CURLRES_ARES
/*
* Curl_resolv_fdset() is called when someone from the outside world (using
* curl_multi_fdset()) wants to get our fd_set setup and we're talking with
* ares. The caller must make sure that this function is only called when we
* have a working ares channel.
*
* Returns: CURLE_OK always!
*/
Daniel Stenberg
committed
int Curl_resolv_getsock(struct connectdata *conn,
curl_socket_t *socks,
int numsocks)
{
Daniel Stenberg
committed
struct timeval maxtime;
struct timeval timebuf;
struct timeval *timeout;
Daniel Stenberg
committed
int max = ares_getsock(conn->data->state.areschannel,
(int *)socks, numsocks);
Daniel Stenberg
committed
maxtime.tv_sec = CURL_TIMEOUT_RESOLVE;
maxtime.tv_usec = 0;
timeout = ares_timeout(conn->data->state.areschannel, &maxtime, &timebuf);
Daniel Stenberg
committed
Curl_expire(conn->data,
(timeout->tv_sec * 1000) + (timeout->tv_usec/1000));
Daniel Stenberg
committed
Daniel Stenberg
committed
return max;
}
Daniel Stenberg
committed
/*
* ares_waitperform()
*
* 1) Ask ares what sockets it currently plays with, then
* 2) wait for the timeout period to check for action on ares' sockets.
* 3) tell ares to act on all the sockets marked as "with action"
*
* return number of sockets it worked on
*/
static int ares_waitperform(struct connectdata *conn, int timeout_ms)
{
struct SessionHandle *data = conn->data;
int nfds;
int bitmask;
int socks[ARES_GETSOCK_MAXNUM];
struct pollfd pfd[ARES_GETSOCK_MAXNUM];
int i;
int num = 0;
Daniel Stenberg
committed
bitmask = ares_getsock(data->state.areschannel, socks, ARES_GETSOCK_MAXNUM);
for(i=0; i < ARES_GETSOCK_MAXNUM; i++) {
pfd[i].events = 0;
pfd[i].revents = 0;
Daniel Stenberg
committed
if(ARES_GETSOCK_READABLE(bitmask, i)) {
pfd[i].fd = socks[i];
pfd[i].events |= POLLRDNORM|POLLIN;
}
if(ARES_GETSOCK_WRITABLE(bitmask, i)) {
pfd[i].fd = socks[i];
pfd[i].events |= POLLWRNORM|POLLOUT;
}
if(pfd[i].events != 0)
num++;
else
Daniel Stenberg
committed
break;
}
if(num)
nfds = Curl_poll(pfd, num, timeout_ms);
else
nfds = 0;
if(!nfds)
/* Call ares_process() unconditonally here, even if we simply timed out
above, as otherwise the ares name resolve won't timeout! */
ares_process_fd(data->state.areschannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
else {
/* move through the descriptors and ask for processing on them */
for(i=0; i < num; i++)
ares_process_fd(data->state.areschannel,
pfd[i].revents & (POLLRDNORM|POLLIN)?
pfd[i].fd:ARES_SOCKET_BAD,
pfd[i].revents & (POLLWRNORM|POLLOUT)?
pfd[i].fd:ARES_SOCKET_BAD);
}
return nfds;
}
/*
* Curl_is_resolved() is called repeatedly to check if a previous name resolve
* request has completed. It should also make sure to time-out if the
* operation seems to take too long.
*
* Returns normal CURLcode errors.
*/
CURLcode Curl_is_resolved(struct connectdata *conn,
struct Curl_dns_entry **dns)
{
struct SessionHandle *data = conn->data;
*dns = NULL;
Daniel Stenberg
committed
ares_waitperform(conn, 0);
if(conn->async.done) {
/* we're done, kill the ares handle */
if(!conn->async.dns) {
failf(data, "Could not resolve host: %s (%s)", conn->host.dispname,
ares_strerror(conn->async.status));
return CURLE_COULDNT_RESOLVE_HOST;
}
*dns = conn->async.dns;
}
return CURLE_OK;
}
/*
* Curl_wait_for_resolv() waits for a resolve to finish. This function should
* be avoided since using this risk getting the multi interface to "hang".
*
* If 'entry' is non-NULL, make it point to the resolved dns entry
*
* Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and
* CURLE_OPERATION_TIMEDOUT if a time-out occurred.
*/
CURLcode Curl_wait_for_resolv(struct connectdata *conn,
struct Curl_dns_entry **entry)
{
CURLcode rc=CURLE_OK;
struct SessionHandle *data = conn->data;
Daniel Stenberg
committed
long timeout;
struct timeval now = Curl_tvnow();
/* now, see if there's a connect timeout or a regular timeout to
use instead of the default one */
if(conn->data->set.connecttimeout)
timeout = conn->data->set.connecttimeout;
else if(conn->data->set.timeout)
timeout = conn->data->set.timeout;
else
Daniel Stenberg
committed
timeout = CURL_TIMEOUT_RESOLVE * 1000; /* default name resolve timeout */
/* Wait for the name resolve query to complete. */
struct timeval *tvp, tv, store;
long timediff;
int itimeout;
itimeout = (timeout > (long)INT_MAX) ? INT_MAX : (int)timeout;
store.tv_sec = itimeout/1000;
store.tv_usec = (itimeout%1000)*1000;
Daniel Stenberg
committed
tvp = ares_timeout(data->state.areschannel, &store, &tv);
/* use the timeout period ares returned to us above */
Yang Tse
committed
ares_waitperform(conn, (int)(tvp->tv_sec * 1000 + tvp->tv_usec/1000));
if(conn->async.done)
Daniel Stenberg
committed
break;
timediff = Curl_tvdiff(Curl_tvnow(), now); /* spent time */
timeout -= timediff?timediff:1; /* always deduct at least 1 */
/* our timeout, so we cancel the ares operation */
ares_cancel(data->state.areschannel);
break;
}
}
/* Operation complete, if the lookup was successful we now have the entry
in the cache. */
Daniel Stenberg
committed
if(entry)
*entry = conn->async.dns;
if(!conn->async.dns) {
/* a name was not resolved */
if((timeout < 0) || (conn->async.status == ARES_ETIMEOUT)) {
Daniel Stenberg
committed
failf(data, "Resolving host timed out: %s", conn->host.dispname);
rc = CURLE_COULDNT_RESOLVE_HOST;
}
else if(conn->async.done) {
Daniel Stenberg
committed
failf(data, "Could not resolve host: %s (%s)", conn->host.dispname,
ares_strerror(conn->async.status));
rc = CURLE_COULDNT_RESOLVE_HOST;
}
else
rc = CURLE_OPERATION_TIMEDOUT;
/* close the connection, since we can't return failure here without
cleaning up this connection properly */
conn->bits.close = TRUE;
}
Daniel Stenberg
committed
return rc;
}
Daniel Stenberg
committed
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
/*
* Curl_ip2addr6() takes an ipv6 internet address as input parameter
* together with a pointer to the string version of the address, and it
* returns a Curl_addrinfo chain filled in correctly with information for this
* address/host.
*
* The input parameters ARE NOT checked for validity but they are expected
* to have been checked already when this is called.
*/
Curl_addrinfo *Curl_ip2addr6(struct in6_addr *in,
const char *hostname, int port)
{
Curl_addrinfo *ai;
#if defined(VMS) && defined(__INITIAL_POINTER_SIZE) && \
(__INITIAL_POINTER_SIZE == 64)
#pragma pointer_size save
#pragma pointer_size short
#pragma message disable PTRMISMATCH
#endif
struct hostent *h;
struct in6_addr *addrentry;
struct namebuf6 {
struct hostent hostentry;
char *h_addr_list[2];
struct in6_addr addrentry;
char hostname[1];
};
struct namebuf6 *buf = malloc(sizeof (struct namebuf6) + strlen(hostname));
if(!buf)
return NULL;
h = &buf->hostentry;
h->h_addr_list = &buf->h_addr_list[0];
addrentry = &buf->addrentry;
memcpy(addrentry, in, sizeof (*in));
h->h_addr_list[0] = (char*)addrentry;
h->h_addr_list[1] = NULL; /* terminate list of entries */
h->h_name = &buf->hostname[0];
h->h_aliases = NULL;
h->h_addrtype = AF_INET6;
/* Now store the dotted version of the address */
strcpy (h->h_name, hostname);
#if defined(VMS) && defined(__INITIAL_POINTER_SIZE) && \
(__INITIAL_POINTER_SIZE == 64)
#pragma pointer_size restore
#pragma message enable PTRMISMATCH
#endif
ai = Curl_he2ai(h, port);
free(buf);
return ai;
}
#endif /* CURLRES_IPV6 */
/*
* Curl_getaddrinfo() - when using ares
*
* Returns name information about the given hostname and port number. If
* successful, the 'hostent' is returned and the forth argument will point to
* memory we need to free after use. That memory *MUST* be freed with
* Curl_freeaddrinfo(), nothing else.
*/
Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
const char *hostname,
int port,
int *waitp)
{
char *bufp;
struct SessionHandle *data = conn->data;
in_addr_t in = inet_addr(hostname);
Daniel Stenberg
committed
int family = PF_INET;
#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
struct in6_addr in6;
#endif /* CURLRES_IPV6 */
*waitp = FALSE;
Daniel Stenberg
committed
/* This is a dotted IP address 123.123.123.123-style */
Daniel Stenberg
committed
return Curl_ip2addr(in, hostname, port);
}
Daniel Stenberg
committed
#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
if (inet_pton (AF_INET6, hostname, &in6) > 0) {
/* This must be an IPv6 address literal. */
return Curl_ip2addr6(&in6, hostname, port);
}
switch(data->set.ip_version) {
case CURL_IPRESOLVE_V4:
family = PF_INET;
break;
default: /* by default we try ipv6, as PF_UNSPEC isn't supported by (c-)ares */
case CURL_IPRESOLVE_V6:
family = PF_INET6;
break;
}
#endif /* CURLRES_IPV6 */
bufp = strdup(hostname);
if(bufp) {
Curl_safefree(conn->async.hostname);
conn->async.hostname = bufp;
conn->async.port = port;
conn->async.done = FALSE; /* not done */
conn->async.status = 0; /* clear */
conn->async.dns = NULL; /* clear */
/* areschannel is already setup in the Curl_open() function */
Daniel Stenberg
committed
ares_gethostbyname(data->state.areschannel, hostname, family,
(ares_host_callback)Curl_addrinfo4_callback, conn);
Daniel Stenberg
committed
*waitp = TRUE; /* please wait for the response */
}
return NULL; /* no struct yet */
}
#endif /* CURLRES_ARES */