diff --git a/ChangeLog b/ChangeLog index 41cc4935..b6b031ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -47,6 +47,8 @@ Changes in 2.0.3-alpha: o Default to using arc4random for DNS transaction IDs on systems that have it; from OpenBSD. o Never check the environment when we're running setuid or setgid; from OpenBSD. o Options passed to evdns_set_option() no longer need to end with a colon. + o Add an evutil_getaddrinfo() function to clone getaddrinfo on platforms that don't have it. + o Add an evdns_getaddrinfo() function to provide a nonblocking getaddrinfo using evdns, so programs can perform useful hostname lookup. Changes in 2.0.2-alpha: diff --git a/Makefile.am b/Makefile.am index e62376d8..b1fa3ca2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -111,7 +111,7 @@ CORE_SRC = event.c buffer.c \ bufferevent.c bufferevent_sock.c bufferevent_filter.c \ bufferevent_pair.c listener.c \ evmap.c log.c evutil.c strlcpy.c $(SYS_SRC) -EXTRA_SRC = event_tagging.c http.c evdns.c evrpc.c bufferevent_evdns.c +EXTRA_SRC = event_tagging.c http.c evdns.c evrpc.c libevent_la_SOURCES = $(CORE_SRC) $(EXTRA_SRC) diff --git a/Makefile.nmake b/Makefile.nmake index 8d56dfac..73e5feee 100644 --- a/Makefile.nmake +++ b/Makefile.nmake @@ -17,7 +17,7 @@ CORE_OBJS=event.obj buffer.obj bufferevent.obj bufferevent_sock.obj \ strlcpy.obj signal.obj bufferevent_filter.obj WIN_OBJS=win32select.obj evthread_win32.obj buffer_iocp.obj \ event_iocp.obj bufferevent_async.obj -EXTRA_OBJS=event_tagging.obj http.obj evdns.obj bufferevent_evdns.obj evrpc.obj +EXTRA_OBJS=event_tagging.obj http.obj evdns.obj evrpc.obj ALL_OBJS=$(CORE_OBJS) $(WIN_OBJS) $(EXTRA_OBJS) STATIC_LIBS=libevent_core.lib libevent_extras.lib libevent.lib diff --git a/WIN32-Code/event-config.h b/WIN32-Code/event-config.h index 5f0e76a3..4a7f6b7e 100644 --- a/WIN32-Code/event-config.h +++ b/WIN32-Code/event-config.h @@ -56,10 +56,16 @@ #define _EVENT_HAVE_FCNTL_H 1 /* Define to 1 if you have the `getaddrinfo' function. */ -/* #undef _EVENT_HAVE_GETADDRINFO */ +#define _EVENT_HAVE_GETADDRINFO 1 /* Define to 1 if you have the `getnameinfo' function. */ -/* #undef _EVENT_HAVE_GETNAMEINFO */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `getprotobynumber' function. */ +#define _EVENT_HAVE_GETPROTOBYNUMBER 1 + +/* Define to 1 if you have the `getservbyname' function. */ +#define _EVENT_HAVE_GETSERVBYNAME 1 /* Define to 1 if you have the `gettimeofday' function. */ /* #define _EVENT_HAVE_GETTIMEOFDAY 1 */ @@ -166,6 +172,8 @@ /* Define to 1 if you have the `strtoll' function. */ /* #define _EVENT_HAVE_STRTOLL 1 */ +#define _EVENT_HAVE_STRUCT_ADDRINFO 1 + /* Define to 1 if the system has the type `struct in6_addr'. */ #define _EVENT_HAVE_STRUCT_IN6_ADDR 1 diff --git a/bufferevent-internal.h b/bufferevent-internal.h index 4ee6d908..732646bd 100644 --- a/bufferevent-internal.h +++ b/bufferevent-internal.h @@ -238,17 +238,6 @@ void _bufferevent_generic_adj_timeouts(struct bufferevent *bev); EVLOCK_UNLOCK(locking->lock, EVTHREAD_WRITE); \ } while(0) -struct evdns_base; -int _bufferevent_socket_connect_hostname_evdns( - struct bufferevent *bufev, - struct evdns_base *evdns_base, - int family, - const char *hostname, - int port); -void _bufferevent_set_socket_connect_hostname_evdns_fn( - int (*fn)(struct bufferevent *, struct evdns_base *, int, - const char *, int)); - #ifdef __cplusplus } #endif diff --git a/bufferevent_evdns.c b/bufferevent_evdns.c deleted file mode 100644 index ae75d552..00000000 --- a/bufferevent_evdns.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2009 Niels Provos, Nick Mathewson - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** @file bufferevent_evdns.c - * - * This module contains code to implement the asynchronous - * resolve-then-connect behavior of bufferevent_socket_connect_hostname. - * - * It isn't part of bufferevent_socket because evdns is in libevent_extras, - * and bufferevent is in libevent_core. - */ - -#ifdef WIN32 -#include -#include -#endif - -#include "event-config.h" - -#include -#ifdef _EVENT_HAVE_SYS_SOCKET_H -#include -#endif -#ifdef _EVENT_HAVE_ARPA_INET_H -#include -#endif -#ifdef _EVENT_HAVE_NETINET_IN_H -#include -#endif -#ifdef _EVENT_HAVE_NETINET_IN6_H -#include -#endif -#include -#include - -#include -#include -#include -#include -#include "bufferevent-internal.h" -#include "mm-internal.h" - -/* Holds info passed to the dns callback */ -struct resolveinfo { - ev_uint8_t family; /* address family that we tried to resolve. */ - ev_uint16_t port; /* port to connect to, in network order. */ - struct bufferevent *bev; /* bufferevent to inform of the resolve. */ -}; - -/* Callback: Invoked when we are done resolving (or failing to resolve) the - * hostname */ -static void -dns_reply_callback(int result, char type, int count, int ttl, void *addresses, - void *arg) -{ - struct resolveinfo *info = arg; - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - struct sockaddr *sa = NULL; - int socklen; - - EVUTIL_ASSERT(info->bev); - BEV_LOCK(info->bev); - - if (result != DNS_ERR_NONE || count == 0) { - _bufferevent_run_eventcb(info->bev, BEV_EVENT_ERROR); - _bufferevent_decref_and_unlock(info->bev); - memset(info, 0, sizeof(*info)); - mm_free(info); - return; - } - - if (type == DNS_IPv4_A) { - EVUTIL_ASSERT(info->family == AF_INET); - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = info->port; - /* XXX handle multiple addresses better */ - sin.sin_addr.s_addr = *(ev_uint32_t*)addresses; - sa = (struct sockaddr*)&sin; - socklen = sizeof(sin); - } else if (type == DNS_IPv6_AAAA) { - EVUTIL_ASSERT(info->family == AF_INET6); - memset(&sin6, 0, sizeof(sin6)); - sin6.sin6_family = AF_INET; - sin6.sin6_port = info->port; - /* XXX handle multiple addresses better */ - memcpy(sin6.sin6_addr.s6_addr, addresses, 16); - sa = (struct sockaddr*)&sin6; - socklen = sizeof(sin6); - } else { - EVUTIL_ASSERT(info->family == AF_INET || - info->family == AF_INET6); - return; /* unreachable */ - } - - bufferevent_socket_connect(info->bev, sa, socklen); - _bufferevent_decref_and_unlock(info->bev); - memset(info, 0, sizeof(*info)); - mm_free(info); -} - -/* Implements the asynchronous-resolve side of - * bufferevent_socket_connect_hostname(). */ -int -_bufferevent_socket_connect_hostname_evdns( - struct bufferevent *bufev, - struct evdns_base *evdns_base, - int family, - const char *hostname, - int port) -{ - struct evdns_request *r; - struct resolveinfo *resolveinfo; - - if (family == AF_UNSPEC) - family = AF_INET; /* XXXX handle "unspec" correctly */ - if (family != AF_INET && family != AF_INET6) - return -1; - if (!bufev || !evdns_base || !hostname) - return -1; - if (port < 1 || port > 65535) - return -1; - - resolveinfo = mm_calloc(1, sizeof(resolveinfo)); - if (!resolveinfo) - return -1; - resolveinfo->family = family; - resolveinfo->port = htons(port); - resolveinfo->bev = bufev; - - if (family == AF_INET) { - r = evdns_base_resolve_ipv4(evdns_base, hostname, 0, - dns_reply_callback, resolveinfo); - } else { - r = evdns_base_resolve_ipv6(evdns_base, hostname, 0, - dns_reply_callback, resolveinfo); - } - - if (!r) { - mm_free(resolveinfo); - return -1; - } - - /* We either need to incref the bufferevent here, or have some code to - * cancel the resolve if the bufferevent gets freed. Let's take the - * first approach. */ - bufferevent_incref(bufev); - return 0; -} - diff --git a/bufferevent_sock.c b/bufferevent_sock.c index f3551a3f..5477d074 100644 --- a/bufferevent_sock.c +++ b/bufferevent_sock.c @@ -380,71 +380,57 @@ done: return result; } -static int (*_bufferevent_socket_connect_hostname_evdns_fn)( - struct bufferevent *, struct evdns_base *, int, - const char *, int) = NULL; - -void _bufferevent_set_socket_connect_hostname_evdns_fn( - int (*fn)(struct bufferevent *, struct evdns_base *, int, - const char *, int)) +static void +bufferevent_connect_getaddrinfo_cb(int result, struct evutil_addrinfo *ai, + void *arg) { - if (!_bufferevent_socket_connect_hostname_evdns_fn) - _bufferevent_socket_connect_hostname_evdns_fn = fn; + struct bufferevent *bev = arg; + int r; + BEV_LOCK(bev); + + if (result != 0) { + /* XXX Communicate the error somehow. */ + _bufferevent_run_eventcb(bev, BEV_EVENT_ERROR); + _bufferevent_decref_and_unlock(bev); + if (ai) + evutil_freeaddrinfo(ai); + return; + } + + /* XXX use the other addrinfos? */ + r = bufferevent_socket_connect(bev, ai->ai_addr, ai->ai_addrlen); + _bufferevent_decref_and_unlock(bev); + evutil_freeaddrinfo(ai); } int bufferevent_socket_connect_hostname(struct bufferevent *bev, struct evdns_base *evdns_base, int family, const char *hostname, int port) { - struct sockaddr_storage ss; - ev_socklen_t socklen = sizeof(ss); - int socklen_int = sizeof(ss); + char portbuf[10]; + struct evutil_addrinfo hint; + int err; if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) return -1; if (port < 1 || port > 65535) return -1; - memset(&ss, 0, sizeof(ss)); - if (!evutil_parse_sockaddr_port(hostname, (struct sockaddr*)&ss, - &socklen_int)) { - socklen = socklen_int; - if (ss.ss_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in*)&ss; - if (family == AF_INET6) - return -1; - if (sin->sin_port) - return -1; - sin->sin_port = htons(port); - } else if (ss.ss_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&ss; - if (family == AF_INET) - return -1; - if (sin6->sin6_port) - return -1; - sin6->sin6_port = htons(port); - } - return bufferevent_socket_connect(bev, (struct sockaddr*)&ss, - socklen); - } + evutil_snprintf(portbuf, sizeof(portbuf), "%d", port); - if (evdns_base) { - EVUTIL_ASSERT(_bufferevent_socket_connect_hostname_evdns_fn); - return _bufferevent_socket_connect_hostname_evdns_fn( - bev, evdns_base, family, hostname, port); - } + memset(&hint, 0, sizeof(hint)); + hint.ai_family = family; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_socktype = SOCK_STREAM; - memset(&ss, 0, sizeof(ss)); + bufferevent_incref(bev); + err = evutil_getaddrinfo_async(evdns_base, hostname, portbuf, + &hint, bufferevent_connect_getaddrinfo_cb, bev); - if (evutil_resolve(family, hostname, (struct sockaddr*)&ss, - &socklen, port)<0) { - _bufferevent_incref_and_lock(bev); - _bufferevent_run_eventcb(bev, BEV_EVENT_ERROR); - _bufferevent_decref_and_unlock(bev); + if (err == 0) + return 0; + else return -1; - } - - return bufferevent_socket_connect(bev, (struct sockaddr*)&ss, socklen); } /* diff --git a/configure.in b/configure.in index 6f826f43..c8913485 100644 --- a/configure.in +++ b/configure.in @@ -170,13 +170,75 @@ die horribly AM_CONDITIONAL(BUILD_WIN32, test x$bwin32 = xtrue) +if test x$bwin32 = xtrue; then + LIBS="$LIBS -lws2_32" +fi + dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_INLINE AC_HEADER_TIME dnl Checks for library functions. -AC_CHECK_FUNCS(gettimeofday vasprintf fcntl clock_gettime strtok_r strsep getaddrinfo getnameinfo strlcpy inet_ntop inet_pton signal sigaction strtoll inet_aton pipe eventfd sendfile mmap splice arc4random issetugid geteuid getegid) +AC_CHECK_FUNCS(gettimeofday vasprintf fcntl clock_gettime strtok_r strsep getaddrinfo getnameinfo strlcpy inet_ntop inet_pton signal sigaction strtoll inet_aton pipe eventfd sendfile mmap splice arc4random issetugid geteuid getegid getservbyname getprotobynumber) + + +# Check for gethostbyname_r in all its glorious incompatible versions. +# (This is cut-and-pasted from Tor, which based its logic on +# Python's configure.in.) +AH_TEMPLATE(HAVE_GETHOSTBYNAME_R, + [Define this if you have any gethostbyname_r()]) + +AC_CHECK_FUNC(gethostbyname_r, [ + AC_MSG_CHECKING([how many arguments gethostbyname_r() wants]) + OLD_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS $MY_CPPFLAGS $MY_THREAD_CPPFLAGS $MY_CFLAGS" + AC_COMPILE_IFELSE(AC_LANG_PROGRAM([ +#include + ], [[ + char *cp1, *cp2; + struct hostent *h1, *h2; + int i1, i2; + (void)gethostbyname_r(cp1,h1,cp2,i1,&h2,&i2); + ]]),[ + AC_DEFINE(HAVE_GETHOSTBYNAME_R) + AC_DEFINE(HAVE_GETHOSTBYNAME_R_6_ARG, 1, + [Define this if gethostbyname_r takes 6 arguments]) + AC_MSG_RESULT(6) + ], [ + AC_TRY_COMPILE([ +#include + ], [ + char *cp1, *cp2; + struct hostent *h1; + int i1, i2; + (void)gethostbyname_r(cp1,h1,cp2,i1,&i2); + ], [ + AC_DEFINE(HAVE_GETHOSTBYNAME_R) + AC_DEFINE(HAVE_GETHOSTBYNAME_R_5_ARG, 1, + [Define this if gethostbyname_r takes 5 arguments]) + AC_MSG_RESULT(5) + ], [ + AC_TRY_COMPILE([ +#include + ], [ + char *cp1; + struct hostent *h1; + struct hostent_data hd; + (void) gethostbyname_r(cp1,h1,&hd); + ], [ + AC_DEFINE(HAVE_GETHOSTBYNAME_R) + AC_DEFINE(HAVE_GETHOSTBYNAME_R_3_ARG, 1, + [Define this if gethostbyname_r takes 3 arguments]) + AC_MSG_RESULT(3) + ], [ + AC_MSG_RESULT(0) + ]) + ]) + ]) + CFLAGS=$OLD_CFLAGS +]) + AC_CHECK_SIZEOF(long) @@ -368,8 +430,9 @@ AC_CHECK_SIZEOF(int) AC_CHECK_SIZEOF(short) AC_CHECK_SIZEOF(size_t) -AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t], , , -[#include +AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t, struct addrinfo], , , +[#define _GNU_SOURCE +#include #ifdef HAVE_NETINET_IN_H #include #endif @@ -379,6 +442,9 @@ AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t], , , #ifdef HAVE_SYS_SOCKET_H #include #endif +#ifdef HAVE_NETDB_H +#include +#endif #ifdef WIN32 #define WIN32_WINNT 0x400 #define _WIN32_WINNT 0x400 diff --git a/evdns.c b/evdns.c index e6d6888a..135d90d3 100644 --- a/evdns.c +++ b/evdns.c @@ -166,8 +166,11 @@ typedef unsigned int uint; #define close _close #endif -#define MAX_ADDRS 32 /* maximum number of addresses from a single packet */ -/* which we bother recording */ +/* maximum number of addresses from a single packet */ +/* that we bother recording */ +#define MAX_V4_ADDRS 32 +#define MAX_V6_ADDRS 32 + #define TYPE_A EVDNS_TYPE_A #define TYPE_CNAME 5 @@ -178,10 +181,10 @@ typedef unsigned int uint; struct evdns_request { u8 *request; /* the dns packet data */ + u8 request_type; /* TYPE_PTR or TYPE_A or TYPE_AAAA */ unsigned int request_len; int reissue_count; int tx_count; /* the number of times that this packet has been sent */ - unsigned int request_type; /* TYPE_PTR or TYPE_A */ void *user_pointer; /* the pointer given to us for this request */ evdns_callback_type user_callback; struct nameserver *ns; /* the server which we last sent it */ @@ -198,23 +201,26 @@ struct evdns_request { struct event timeout_event; u16 trans_id; /* the transaction id */ - char request_appended; /* true if the request pointer is data which follows this struct */ - char transmit_me; /* needs to be transmitted */ + unsigned request_appended :1; /* true if the request pointer is data which follows this struct */ + unsigned transmit_me :1; /* needs to be transmitted */ + + /* XXXX This is a horrible hack. */ + char **put_cname_in_ptr; /* store the cname here if we get one. */ struct evdns_base *base; }; struct reply { unsigned int type; - unsigned int have_answer; + unsigned int have_answer : 1; union { struct { u32 addrcount; - u32 addresses[MAX_ADDRS]; + u32 addresses[MAX_V4_ADDRS]; } a; struct { u32 addrcount; - struct in6_addr addresses[MAX_ADDRS]; + struct in6_addr addresses[MAX_V6_ADDRS]; } aaaa; struct { char name[HOST_NAME_MAX]; @@ -332,7 +338,7 @@ struct evdns_base { int global_max_requests_inflight; - struct timeval global_timeout; /* 5 seconds */ + struct timeval global_timeout; /* 5 seconds by default */ int global_max_reissues; /* a reissue occurs when we get some errors from the server */ int global_max_retransmits; /* number of times we'll retransmit a request which timed out */ /* number of timeouts in a row before we consider this server to be down */ @@ -345,6 +351,13 @@ struct evdns_base { /** ev_socklen_t for global_outgoing_address. 0 if it isn't set. */ ev_socklen_t global_outgoing_addrlen; + struct timeval global_getaddrinfo_allow_skew; + + int getaddrinfo_ipv4_timeouts; + int getaddrinfo_ipv6_timeouts; + int getaddrinfo_ipv4_answered; + int getaddrinfo_ipv6_answered; + struct search_state *global_search_state; #ifndef _EVENT_DISABLE_THREAD_SUPPORT @@ -355,6 +368,12 @@ struct evdns_base { static struct evdns_base *current_base = NULL; +struct evdns_base * +evdns_get_global_base(void) +{ + return current_base; +} + /* Given a pointer to an evdns_server_request, get the corresponding */ /* server_request. */ #define TO_SERVER_REQUEST(base_ptr) \ @@ -1019,7 +1038,7 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) { if ((datalength & 3) != 0) /* not an even number of As. */ goto err; addrcount = datalength >> 2; - addrtocopy = MIN(MAX_ADDRS - reply.data.a.addrcount, (unsigned)addrcount); + addrtocopy = MIN(MAX_V4_ADDRS - reply.data.a.addrcount, (unsigned)addrcount); ttl_r = MIN(ttl_r, ttl); /* we only bother with the first four addresses. */ @@ -1029,7 +1048,7 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) { j += 4*addrtocopy; reply.data.a.addrcount += addrtocopy; reply.have_answer = 1; - if (reply.data.a.addrcount == MAX_ADDRS) break; + if (reply.data.a.addrcount == MAX_V4_ADDRS) break; } else if (type == TYPE_PTR && class == CLASS_INET) { if (req->request_type != TYPE_PTR) { j += datalength; continue; @@ -1040,6 +1059,15 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) { ttl_r = MIN(ttl_r, ttl); reply.have_answer = 1; break; + } else if (type == TYPE_CNAME) { + char cname[HOST_NAME_MAX]; + if (!req->put_cname_in_ptr || *req->put_cname_in_ptr) { + j += datalength; continue; + } + if (name_parse(packet, length, &j, cname, + sizeof(cname))<0) + goto err; + *req->put_cname_in_ptr = mm_strdup(cname); } else if (type == TYPE_AAAA && class == CLASS_INET) { int addrcount, addrtocopy; if (req->request_type != TYPE_AAAA) { @@ -1048,7 +1076,7 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) { if ((datalength & 15) != 0) /* not an even number of AAAAs. */ goto err; addrcount = datalength >> 4; /* each address is 16 bytes long */ - addrtocopy = MIN(MAX_ADDRS - reply.data.aaaa.addrcount, (unsigned)addrcount); + addrtocopy = MIN(MAX_V6_ADDRS - reply.data.aaaa.addrcount, (unsigned)addrcount); ttl_r = MIN(ttl_r, ttl); /* we only bother with the first four addresses. */ @@ -1058,7 +1086,7 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) { reply.data.aaaa.addrcount += addrtocopy; j += 16*addrtocopy; reply.have_answer = 1; - if (reply.data.aaaa.addrcount == MAX_ADDRS) break; + if (reply.data.aaaa.addrcount == MAX_V6_ADDRS) break; } else { /* skip over any other type of resource */ j += datalength; @@ -3206,6 +3234,14 @@ evdns_base_set_option_impl(struct evdns_base *base, if (!(flags & DNS_OPTION_MISC)) return 0; log(EVDNS_LOG_DEBUG, "Setting timeout to %s", val); memcpy(&base->global_timeout, &tv, sizeof(struct timeval)); + } else if (str_matches_option(option, "getaddrinfo-allow-skew:")) { + struct timeval tv; + if (strtotimeval(val, &tv) == -1) return -1; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting getaddrinfo-allow-skew to %s", + val); + memcpy(&base->global_getaddrinfo_allow_skew, &tv, + sizeof(struct timeval)); } else if (str_matches_option(option, "max-timeouts:")) { const int maxtimeout = strtoint_clipped(val, 1, 255); if (maxtimeout == -1) return -1; @@ -3605,11 +3641,10 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers) { struct evdns_base *base; - /* Give the bufferevent library a hook into its evdns-enabled - * functionality. We can't do this correctly or else libevent-core - * will depend on libevent-extras. */ - _bufferevent_set_socket_connect_hostname_evdns_fn( - _bufferevent_socket_connect_hostname_evdns); + /* Give the evutil library a hook into its evdns-enabled + * functionality. We can't just call evdns_getaddrinfo directly or + * else libevent-core will depend on libevent-extras. */ + evutil_set_evdns_getaddrinfo_fn(evdns_getaddrinfo); base = mm_malloc(sizeof(struct evdns_base)); if (base == NULL) @@ -3637,6 +3672,8 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers) base->global_max_nameserver_timeout = 3; base->global_search_state = NULL; base->global_randomize_case = 1; + base->global_getaddrinfo_allow_skew.tv_sec = 3; + base->global_getaddrinfo_allow_skew.tv_usec = 0; if (initialize_nameservers) { int r; @@ -3757,3 +3794,419 @@ evdns_shutdown(int fail_requests) evdns_log_fn = NULL; } +/* A single request for a getaddrinfo, either v4 or v6. */ +struct getaddrinfo_subrequest { + struct evdns_request *r; + ev_uint32_t type; +}; + +/* State data used to implement an in-progress getaddrinfo. */ +struct evdns_getaddrinfo_request { + struct evdns_base *evdns_base; + /* Copy of the modified 'hints' data that we'll use to build + * answers. */ + struct evutil_addrinfo hints; + /* The callback to invoke when we're done */ + evdns_getaddrinfo_cb user_cb; + /* User-supplied data to give to the callback. */ + void *user_data; + /* The port to use when building sockaddrs. */ + ev_uint16_t port; + /* The sub_request for an A record (if any) */ + struct getaddrinfo_subrequest ipv4_request; + /* The sub_request for an AAAA record (if any) */ + struct getaddrinfo_subrequest ipv6_request; + + /* The cname result that we were told (if any) */ + char *cname_result; + + /* If we have one request answered and one request still inflight, + * then this field holds the answer from the first request... */ + struct evutil_addrinfo *pending_result; + /* And this field holds the error code from the first request... */ + int pending_error; + /* And this event is a timeout that will tell us to cancel the second + * request if it's taking a long time. */ + struct event timeout; +}; + +/* Convert an evdns errors to the equivalent getaddrinfo error. */ +static int +evdns_err_to_getaddrinfo_err(int e1) +{ + /* XXX Do this better! */ + if (e1 == DNS_ERR_NONE) + return 0; + else if (e1 == DNS_ERR_NOTEXIST) + return EVUTIL_EAI_NONAME; + else + return EVUTIL_EAI_FAIL; +} + +/* Return the more informative of two getaddrinfo errors. */ +static int +getaddrinfo_merge_err(int e1, int e2) +{ + /* XXXX be cleverer here. */ + if (e1 == 0) + return e2; + else + return e1; +} + +static void +free_getaddrinfo_request(struct evdns_getaddrinfo_request *data) +{ + if (data->pending_result) + evutil_freeaddrinfo(data->pending_result); + if (data->cname_result) + mm_free(data->cname_result); + event_del(&data->timeout); + mm_free(data); + return; +} + +static void +add_cname_to_reply(struct evdns_getaddrinfo_request *data, + struct evutil_addrinfo *ai) +{ + if (data->cname_result && ai) { + ai->ai_canonname = data->cname_result; + data->cname_result = NULL; + } +} + +/* Callback: invoked when one request in a mixed-format A/AAAA getaddrinfo + * request has finished, but the other one took too long to answer. Pass + * along the answer we got, and cancel the other request. + */ +static void +evdns_getaddrinfo_timeout_cb(evutil_socket_t fd, short what, void *ptr) +{ + int v4_timedout = 0, v6_timedout = 0; + struct evdns_getaddrinfo_request *data = ptr; + + /* Cancel any pending requests, and note which one */ + if (data->ipv4_request.r) { + evdns_cancel_request(NULL, data->ipv4_request.r); + data->ipv4_request.r = NULL; + v4_timedout = 1; + EVDNS_LOCK(data->evdns_base); + ++data->evdns_base->getaddrinfo_ipv4_timeouts; + } + if (data->ipv6_request.r) { + evdns_cancel_request(NULL, data->ipv6_request.r); + data->ipv6_request.r = NULL; + v6_timedout = 1; + EVDNS_LOCK(data->evdns_base); + ++data->evdns_base->getaddrinfo_ipv6_timeouts; + EVDNS_UNLOCK(data->evdns_base); + } + + /* We only use this timeout callback when we have an answer for + * one address. */ + EVUTIL_ASSERT(!v4_timedout || !v6_timedout); + + /* Report the outcome of the other request that didn't time out. */ + if (data->pending_result) { + add_cname_to_reply(data, data->pending_result); + data->user_cb(0, data->pending_result, data->user_data); + data->pending_result = NULL; + } else { + int e = data->pending_error; + if (!e) + e = EVUTIL_EAI_AGAIN; + data->user_cb(e, NULL, data->user_data); + } + + free_getaddrinfo_request(data); +} + +static void +evdns_getaddrinfo_set_timeout(struct evdns_base *evdns_base, + struct evdns_getaddrinfo_request *data) +{ + event_add(&data->timeout, &evdns_base->global_getaddrinfo_allow_skew); +} + +static void +evdns_getaddrinfo_gotresolve(int result, char type, int count, + int ttl, void *addresses, void *arg) +{ + int i; + struct getaddrinfo_subrequest *req = arg; + struct getaddrinfo_subrequest *other_req; + struct evdns_getaddrinfo_request *data; + + struct evutil_addrinfo *res; + + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr *sa; + int socklen, addrlen; + void *addrp; + int err; + + if (result == DNS_ERR_CANCEL) + return; + + EVUTIL_ASSERT(req->type == DNS_IPv4_A || req->type == DNS_IPv6_AAAA); + if (req->type == DNS_IPv4_A) { + data = EVUTIL_UPCAST(req, struct evdns_getaddrinfo_request, ipv4_request); + other_req = &data->ipv6_request; + if (result != DNS_ERR_NOTIMPL && result != DNS_ERR_REFUSED && + result != DNS_ERR_SERVERFAILED) { + EVDNS_LOCK(data->evdns_base); + ++data->evdns_base->getaddrinfo_ipv4_answered; + EVDNS_UNLOCK(data->evdns_base); + } + } else { + data = EVUTIL_UPCAST(req, struct evdns_getaddrinfo_request, ipv6_request); + other_req = &data->ipv4_request; + if (result != DNS_ERR_NOTIMPL && result != DNS_ERR_REFUSED && + result != DNS_ERR_SERVERFAILED) { + EVDNS_LOCK(data->evdns_base); + ++data->evdns_base->getaddrinfo_ipv6_answered; + EVDNS_UNLOCK(data->evdns_base); + } + } + + req->r = NULL; + + if (result == DNS_ERR_NONE) { + if (count == 0) + err = EVUTIL_EAI_NODATA; + else + err = 0; + } else { + err = evdns_err_to_getaddrinfo_err(result); + } + + if (err) { + /* Looks like we got an error. */ + if (other_req->r) { + /* The other request is still working; maybe it will + * succeed. */ + evdns_getaddrinfo_set_timeout(data->evdns_base, data); + data->pending_error = err; + return; + } + + if (data->pending_result) { + /* If we have an answer waiting, ignore this error. */ + add_cname_to_reply(data, data->pending_result); + data->user_cb(0, data->pending_result, data->user_data); + data->pending_result = NULL; + } else { + if (data->pending_error) + err = getaddrinfo_merge_err(err, + data->pending_error); + data->user_cb(err, NULL, data->user_data); + } + free_getaddrinfo_request(data); + return; + } + + /* Looks like we got some answers. We should turn them into addrinfos + * and then either queue those or return them all. */ + EVUTIL_ASSERT(type == DNS_IPv4_A || type == DNS_IPv6_AAAA); + + if (type == DNS_IPv4_A) { + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(data->port); + + sa = (struct sockaddr *)&sin; + socklen = sizeof(sin); + addrlen = 4; + addrp = &sin.sin_addr.s_addr; + } else { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(data->port); + + sa = (struct sockaddr *)&sin6; + socklen = sizeof(sin6); + addrlen = 16; + addrp = &sin6.sin6_addr.s6_addr; + } + + res = NULL; + for (i=0; i < count; ++i) { + struct evutil_addrinfo *ai; + memcpy(addrp, ((char*)addresses)+i*addrlen, addrlen); + ai = evutil_new_addrinfo(sa, socklen, &data->hints); + if (!ai) { + if (other_req->r) { + evdns_cancel_request(NULL, other_req->r); + other_req->r = NULL; + } + data->user_cb(EVUTIL_EAI_MEMORY, NULL, data->user_data); + evutil_freeaddrinfo(res); + + free_getaddrinfo_request(data); + return; + } + res = evutil_addrinfo_append(res, ai); + } + + if (other_req->r) { + /* The other request is still in progress; wait for it */ + evdns_getaddrinfo_set_timeout(data->evdns_base, data); + data->pending_result = res; + return; + } else { + /* The other request is done or never started; append its + * results (if any) and return them. */ + if (data->pending_result) { + if (req->type == DNS_IPv4_A) + res = evutil_addrinfo_append(res, + data->pending_result); + else + res = evutil_addrinfo_append( + data->pending_result, res); + data->pending_result = NULL; + } + + /* Call the user callback. */ + add_cname_to_reply(data, res); + data->user_cb(0, res, data->user_data); + + /* Free data. */ + free_getaddrinfo_request(data); + } +} + +struct evdns_getaddrinfo_request * +evdns_getaddrinfo(struct evdns_base *dns_base, + const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, + evdns_getaddrinfo_cb cb, void *arg) +{ + struct evdns_getaddrinfo_request *data; + struct evutil_addrinfo hints; + struct evutil_addrinfo *res = NULL; + int err; + int port = 0; + int want_cname = 0; + + if (!dns_base) { + dns_base = current_base; + if (!dns_base) { + log(EVDNS_LOG_WARN, + "Call to getaddrinfo_async with no " + "evdns_base configured."); + cb(EVUTIL_EAI_FAIL, NULL, arg); /* ??? better error? */ + return NULL; + } + } + + /* If we _must_ answer this immediately, do so. */ + if ((hints_in && (hints_in->ai_flags & EVUTIL_AI_NUMERICHOST))) { + res = NULL; + err = evutil_getaddrinfo(nodename, servname, hints_in, &res); + cb(err, res, arg); + return NULL; + } + + if (hints_in) { + memcpy(&hints, hints_in, sizeof(hints)); + } else { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + } + + evutil_adjust_hints_for_addrconfig(&hints); + + /* Now try to see if we _can_ answer immediately. */ + /* (It would be nice to do this by calling getaddrinfo directly, with + * AI_NUMERICHOST, on plaforms that have it, but we can't: there isn't + * a reliable way to distinguish the "that wasn't a numeric host!" case + * from any other EAI_NONAME cases.) */ + err = evutil_getaddrinfo_common(nodename, servname, &hints, &res, &port); + if (err != EVUTIL_EAI_NEED_RESOLVE) { + cb(err, res, arg); + return NULL; + } + + /* Okay, things are serious now. We're going to need to actually + * launch a request. + */ + data = mm_calloc(1,sizeof(struct evdns_getaddrinfo_request)); + if (!data) { + cb(EVUTIL_EAI_MEMORY, NULL, arg); + return NULL; + } + + memcpy(&data->hints, &hints, sizeof(data->hints)); + data->port = (ev_uint16_t)port; + data->ipv4_request.type = DNS_IPv4_A; + data->ipv6_request.type = DNS_IPv6_AAAA; + data->user_cb = cb; + data->user_data = arg; + data->evdns_base = dns_base; + + want_cname = (hints.ai_flags & EVUTIL_AI_CANONNAME); + + /* If we are asked for a PF_UNSPEC address, we launch two requests in + * parallel: one for an A address and one for an AAAA address. We + * can't send just one request, since many servers only answer one + * question per DNS request. + * + * Once we have the answer to one request, we allow for a short + * timeout before we report it, to see if the other one arrives. If + * they both show up in time, then we report both the answers. + * + * If too many addresses of one type time out or fail, we should stop + * launching those requests. (XXX we don't do that yet.) + */ + + if (hints.ai_family != PF_INET6) { + log(EVDNS_LOG_DEBUG, "Sending request for %s on ipv4 as %p", + nodename, &data->ipv4_request); + + data->ipv4_request.r = evdns_base_resolve_ipv4(dns_base, + nodename, 0, evdns_getaddrinfo_gotresolve, + &data->ipv4_request); + if (want_cname) + data->ipv4_request.r->put_cname_in_ptr = + &data->cname_result; + } + if (hints.ai_family != PF_INET) { + log(EVDNS_LOG_DEBUG, "Sending request for %s on ipv6 as %p", + nodename, &data->ipv6_request); + + data->ipv6_request.r = evdns_base_resolve_ipv6(dns_base, + nodename, 0, evdns_getaddrinfo_gotresolve, + &data->ipv6_request); + if (want_cname) + data->ipv6_request.r->put_cname_in_ptr = + &data->cname_result; + } + + evtimer_assign(&data->timeout, dns_base->event_base, + evdns_getaddrinfo_timeout_cb, data); + + if (data->ipv4_request.r || data->ipv6_request.r) { + return data; + } else { + mm_free(data); + cb(EVUTIL_EAI_FAIL, NULL, arg); + return NULL; + } +} + +void +evdns_getaddrinfo_cancel(struct evdns_getaddrinfo_request *data) +{ + event_del(&data->timeout); + if (data->ipv4_request.r) + evdns_cancel_request(data->evdns_base, data->ipv4_request.r); + if (data->ipv6_request.r) + evdns_cancel_request(data->evdns_base, data->ipv6_request.r); + data->ipv4_request.r = data->ipv6_request.r = NULL; + + data->user_cb(EVUTIL_EAI_CANCEL, NULL, data->user_data); + + free_getaddrinfo_request(data); +} diff --git a/evutil.c b/evutil.c index 072b22b4..b67ee73c 100644 --- a/evutil.c +++ b/evutil.c @@ -26,6 +26,9 @@ #include "event-config.h" +#define _REENTRANT +#define _GNU_SOURCE + #ifdef WIN32 #include #include @@ -59,9 +62,6 @@ #ifdef _EVENT_HAVE_NETINET_IN6_H #include #endif -#ifdef _EVENT_HAVE_NETDB_H -#include -#endif #ifndef _EVENT_HAVE_GETTIMEOFDAY #include @@ -71,6 +71,7 @@ #include "event2/util.h" #include "util-internal.h" #include "log-internal.h" +#include "mm-internal.h" #include "strlcpy-internal.h" #include "ipv6-internal.h" @@ -322,92 +323,683 @@ evutil_socket_finished_connecting(evutil_socket_t fd) return 1; } -/** Internal helper: use the host's (blocking) resolver to look up 'hostname', - * and set the sockaddr pointed to by 'sa' to the answer. Assume we have - * *socklen bytes of storage; adjust *socklen to the number of bytes used. - * Try to return answers of type 'family', unless family is AF_UNSPEC. - * Return 0 on success and -1 on failure. If 'port' is nonzero, it is - * a port number in host order: set the port in any resulting sockaddr to - * the specified port. +/* We sometimes need to know whether we have an ipv4 address and whether we + have an ipv6 address. If 'have_checked_interfaces', then we've already done + the test. If 'had_ipv4_address', then it turns out we had an ipv4 address. + If 'had_ipv6_address', then it turns out we had an ipv6 address. These are + set by evutil_check_interfaces. */ +static int have_checked_interfaces, had_ipv4_address, had_ipv6_address; + +/* Test whether we have an ipv4 interface and an ipv6 interface. Return 0 if + * the test seemed successful. */ +static int +evutil_check_interfaces(int force_recheck) +{ + const char ZEROES[] = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00"; + evutil_socket_t fd = -1; + struct sockaddr_in sin, sin_out; + struct sockaddr_in6 sin6, sin6_out; + ev_socklen_t sin_out_len = sizeof(sin_out); + ev_socklen_t sin6_out_len = sizeof(sin6_out); + int r; + char buf[128]; + if (have_checked_interfaces && !force_recheck) + return 0; + + /* To check whether we have an interface open for a given protocol, we + * try to make a UDP 'connection' to a remote host on the internet. + * We don't actually use it, so the address doesn't matter, but we + * want to pick one that keep us from using a host- or link-local + * interface. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(53); + r = evutil_inet_pton(AF_INET, "18.244.0.188", &sin.sin_addr); + EVUTIL_ASSERT(r); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(53); + r = evutil_inet_pton(AF_INET6, "2001:4860:b002::68", &sin6.sin6_addr); + EVUTIL_ASSERT(r); + + memset(&sin_out, 0, sizeof(sin_out)); + memset(&sin6_out, 0, sizeof(sin6_out)); + + /* XXX some errnos mean 'no address'; some mean 'not enough sockets'. */ + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) >= 0 && + connect(fd, (struct sockaddr*)&sin, sizeof(sin)) == 0 && + getsockname(fd, (struct sockaddr*)&sin_out, &sin_out_len) == 0) { + /* We might have an IPv4 interface. */ + ev_uint32_t addr = ntohl(sin_out.sin_addr.s_addr); + if (addr == 0 || (addr&0xff000000) == 127 || + (addr && 0xff) == 255 || (addr & 0xf0) == 14) { + evutil_inet_ntop(AF_INET, &sin_out.sin_addr, + buf, sizeof(buf)); + /* This is a reserved, ipv4compat, ipv4map, loopback, + * link-local or unspecified address. The host should + * never have given it to us; it could never connect + * to sin. */ + event_warnx("Got a strange local ipv4 address %s",buf); + } else { + event_debug(("Detected an IPv4 interface")); + had_ipv4_address = 1; + } + } + if (fd >= 0) + EVUTIL_CLOSESOCKET(fd); + + if ((fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) >= 0 && + connect(fd, (struct sockaddr*)&sin6, sizeof(sin6)) == 0 && + getsockname(fd, (struct sockaddr*)&sin6_out, &sin6_out_len) == 0) { + /* We might have an IPv6 interface. */ + const unsigned char *addr = + (unsigned char*)sin6_out.sin6_addr.s6_addr; + if (!memcmp(addr, ZEROES, 8) || + (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80)) { + /* This is a reserved, ipv4compat, ipv4map, loopback, + * link-local or unspecified address. The host should + * never have given it to us; it could never connect + * to sin6. */ + evutil_inet_ntop(AF_INET6, &sin6_out.sin6_addr, + buf, sizeof(buf)); + event_warnx("Got a strange local ipv6 address %s",buf); + } else { + event_debug(("Detected an IPv4 interface")); + had_ipv6_address = 1; + } + } + + if (fd >= 0) + EVUTIL_CLOSESOCKET(fd); + + return 0; +} + +/* Internal addrinfo flag. This one is set when we allocate the addrinfo from + * inside libevent. Otherwise, the built-in getaddrinfo() function allocated + * it, and we should trust what they said. + **/ +#define EVUTIL_AI_LIBEVENT_ALLOCATED 0x80000000 + +/* Helper: construct a new addrinfo containing the socket address in + * 'sa', which must be a sockaddr_in or a sockaddr_in6. Take the + * socktype and protocol info from hints. If they weren't set, then + * allocate both a TCP and a UDP addrinfo. + */ +struct evutil_addrinfo * +evutil_new_addrinfo(struct sockaddr *sa, ev_socklen_t socklen, + const struct evutil_addrinfo *hints) +{ + size_t extra; + struct evutil_addrinfo *res; + EVUTIL_ASSERT(hints); + + if (hints->ai_socktype == 0 && hints->ai_protocol == 0) { + /* Indecisive user! Give them a UDP and a TCP. */ + struct evutil_addrinfo *r1, *r2; + struct evutil_addrinfo tmp; + memcpy(&tmp, hints, sizeof(tmp)); + tmp.ai_socktype = SOCK_STREAM; tmp.ai_protocol = IPPROTO_TCP; + r1 = evutil_new_addrinfo(sa, socklen, &tmp); + if (!r1) + return NULL; + tmp.ai_socktype = SOCK_DGRAM; tmp.ai_protocol = IPPROTO_UDP; + r2 = evutil_new_addrinfo(sa, socklen, &tmp); + if (!r2) { + evutil_freeaddrinfo(r2); + return NULL; + } + r1->ai_next = r2; + return r1; + } + + /* We're going to allocate extra space to hold the sockaddr. */ + extra = (hints->ai_family == PF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6); + res = mm_calloc(1,sizeof(struct evutil_addrinfo)+socklen); + if (!res) + return NULL; + res->ai_addr = (struct sockaddr*) + (((char*)res) + sizeof(struct evutil_addrinfo)); + memcpy(res->ai_addr, sa, socklen); + res->ai_addrlen = socklen; + res->ai_family = sa->sa_family; /* Same or not? XXX */ + res->ai_flags = EVUTIL_AI_LIBEVENT_ALLOCATED; + res->ai_socktype = hints->ai_socktype; + res->ai_protocol = hints->ai_protocol; + + return res; +} + +/* Append the addrinfo 'append' to the end of 'first', and return the start of + * the list. Either element can be NULL, in which case we return the element + * that is not NULL. */ +struct evutil_addrinfo * +evutil_addrinfo_append(struct evutil_addrinfo *first, + struct evutil_addrinfo *append) +{ + struct evutil_addrinfo *ai = first; + if (!ai) + return append; + while (ai->ai_next) + ai = ai->ai_next; + ai->ai_next = append; + + return first; +} + +/** Parse a service name in 'servname', which can be a decimal port. + * Return the port number, or -1 on error. + */ +static int +evutil_parse_servname(const char *servname, const char *protocol, + const struct evutil_addrinfo *hints) +{ + int n; + char *endptr=NULL; + n = (int) strtol(servname, &endptr, 10); + if (n>=0 && n <= 65535 && servname[0] && endptr && !endptr[0]) + return n; +#ifdef _EVENT_HAVE_GETSERVBYNAME + if (!(hints->ai_flags & EVUTIL_AI_NUMERICSERV)) { + struct servent *ent = getservbyname(servname, protocol); + if (ent) { + return ntohs(ent->s_port); + } + } +#endif + return -1; +} + +/* Return a string corresponding to a protocol number that we can pass to + * getservyname. */ +static const char * +evutil_unparse_protoname(int proto) +{ + if (proto == 0) + return NULL; + else if (proto == IPPROTO_TCP) + return "tcp"; + else if (proto == IPPROTO_UDP) + return "udp"; +#ifdef IPPROTO_SCTP + else if (proto == IPPROTO_SCTP) + return "sctp"; +#endif +#ifdef _EVENT_HAVE_GETPROTOBYNUMBER + { + struct protoent *ent = getprotobynumber(proto); + if (ent) + return ent->p_name; + } +#endif + return NULL; +} + +static void +evutil_getaddrinfo_infer_protocols(struct evutil_addrinfo *hints) +{ + /* If we can guess the protocol from the socktype, do so. */ + if (!hints->ai_protocol && hints->ai_socktype) { + if (hints->ai_socktype == SOCK_DGRAM) + hints->ai_protocol = IPPROTO_UDP; + else if (hints->ai_socktype == SOCK_STREAM) + hints->ai_protocol = IPPROTO_TCP; + } + + /* Set the socktype if it isn't set. */ + if (!hints->ai_socktype && hints->ai_protocol) { + if (hints->ai_protocol == IPPROTO_UDP) + hints->ai_socktype = SOCK_DGRAM; + else if (hints->ai_protocol == IPPROTO_TCP) + hints->ai_socktype = SOCK_STREAM; +#ifdef IPPROTO_SCTP + else if (hints->ai_protocol == IPPROTO_SCTP) + hints->ai_socktype = SOCK_STREAM; +#endif + } +} + +/** Implements the part of looking up hosts by name that's common to both + * the blocking and nonblocking resolver: + * - Adjust 'hints' to have a reasonable socktype and protocol. + * - Look up the port based on 'servname', and store it in *portnum, + * - Handle the nodename==NULL case + * - Handle some invalid arguments cases. + * - Handle the cases where nodename is an IPv4 or IPv6 address. + * + * If we need the resolver to look up the hostname, we return + * EVUTIL_EAI_NEED_RESOLVE. Otherwise, we can completely implement + * getaddrinfo: we return 0 or an appropriate EVUTIL_EAI_* error, and + * set *res as getaddrinfo would. */ int -evutil_resolve(int family, const char *hostname, struct sockaddr *sa, - ev_socklen_t *socklen, int port) +evutil_getaddrinfo_common(const char *nodename, const char *servname, + struct evutil_addrinfo *hints, struct evutil_addrinfo **res, int *portnum) { -#ifdef _EVENT_HAVE_GETADDRINFO_XXX - struct addrinfo hint, *hintp=NULL; - struct addrinfo *ai=NULL; - int r; - memset(&hint, 0, sizeof(hint)); + int port = 0; + const char *pname; - if (family != AF_UNSPEC) { - hint.ai_family = family; - hintp = &hint; + if (nodename == NULL && servname == NULL) + return EVUTIL_EAI_NONAME; + + /* We only understand 3 families */ + if (hints->ai_family != PF_UNSPEC && hints->ai_family != PF_INET && + hints->ai_family != PF_INET6) + return EVUTIL_EAI_FAMILY; + + evutil_getaddrinfo_infer_protocols(hints); + + /* Look up the port number and protocol, if possible. */ + pname = evutil_unparse_protoname(hints->ai_protocol); + if (servname) { + /* XXXX We could look at the protocol we got back from + * getservbyname, but it doesn't seem too useful. */ + port = evutil_parse_servname(servname, pname, hints); + if (port < 0) { + return EVUTIL_EAI_NONAME; + } } - r = getaddrinfo(hostname, NULL, hintp, &ai); - if (!ai) - return -1; - if (r || ai->ai_addrlen > *socklen) { - /* log/report error? */ - freeaddrinfo(ai); - return -1; + /* If we have no node name, then we're supposed to bind to 'any' and + * connect to localhost. */ + if (nodename == NULL) { + struct evutil_addrinfo *res4=NULL, *res6=NULL; + if (hints->ai_family != PF_INET) { /* INET6 or UNSPEC. */ + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + if (hints->ai_flags & AI_PASSIVE) { + /* Bind to :: */ + } else { + /* connect to ::1 */ + sin6.sin6_addr.s6_addr[15] = 1; + } + res6 = evutil_new_addrinfo((struct sockaddr*)&sin6, + sizeof(sin6), hints); + if (!res6) + return EVUTIL_EAI_MEMORY; + } + + if (hints->ai_family != PF_INET6) { /* INET or UNSPEC */ + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + if (hints->ai_flags & AI_PASSIVE) { + /* Bind to 0.0.0.0 */ + } else { + /* connect to 127.0.0.1 */ + sin.sin_addr.s_addr = htonl(0x7f000001); + } + res4 = evutil_new_addrinfo((struct sockaddr*)&sin, + sizeof(sin), hints); + if (!res4) { + if (res6) + evutil_freeaddrinfo(res6); + return EVUTIL_EAI_MEMORY; + } + } + *res = evutil_addrinfo_append(res4, res6); + return 0; } - /* XXX handle multiple return values better. */ - memcpy(sa, ai->ai_addr, ai->ai_addrlen); - if (port) { - if (sa->sa_family == AF_INET) - ((struct sockaddr_in*)sa)->sin_port = htons(port); - else if (sa->sa_family == AF_INET6) - ((struct sockaddr_in6*)sa)->sin6_port = htons(port); + + /* If we can, we should try to parse the hostname without resolving + * it. */ + /* Try ipv6. */ + if (hints->ai_family == PF_INET6 || hints->ai_family == PF_UNSPEC){ + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + if (1==evutil_inet_pton(AF_INET6, nodename, &sin6.sin6_addr)) { + /* Got an ipv6 address. */ + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + *res = evutil_new_addrinfo((struct sockaddr*)&sin6, + sizeof(sin6), hints); + if (!*res) + return EVUTIL_EAI_MEMORY; + return 0; + } } - *socklen = ai->ai_addrlen; - freeaddrinfo(ai); - return 0; -#else - /* XXXX use gethostbyname_r/gethostbyname2_r where available */ - struct hostent *he; - struct sockaddr *sa_ptr; + + /* Try ipv4. */ + if (hints->ai_family == PF_INET || hints->ai_family == PF_UNSPEC) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + if (1==evutil_inet_pton(AF_INET, nodename, &sin.sin_addr)) { + /* Got an ipv6 address. */ + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + *res = evutil_new_addrinfo((struct sockaddr*)&sin, + sizeof(sin), hints); + if (!*res) + return EVUTIL_EAI_MEMORY; + return 0; + } + } + + + /* If we have reached this point, we definitely need to do a DNS + * lookup. */ + if ((hints->ai_flags & EVUTIL_AI_NUMERICHOST)) { + /* If we're not allowed to do one, then say so. */ + return EVUTIL_EAI_NONAME; + } + *portnum = port; + return EVUTIL_EAI_NEED_RESOLVE; +} + +#ifdef _EVENT_HAVE_GETADDRINFO +#define USE_NATIVE_GETADDRINFO +#endif + +#ifndef USE_NATIVE_GETADDRINFO +/* Helper for systems with no getaddrinfo(): make one or more addrinfos out of + * a struct hostent. + */ +static struct evutil_addrinfo * +addrinfo_from_hostent(const struct hostent *ent, + int port, const struct evutil_addrinfo *hints) +{ + int i; struct sockaddr_in sin; struct sockaddr_in6 sin6; - ev_socklen_t slen; - he = gethostbyname(hostname); - if (!he || !he->h_length) { - return -1; - } - /* XXX handle multiple return values better. */ - if (he->h_addrtype == AF_INET) { - if (family != AF_INET && family != AF_UNSPEC) - return -1; + struct sockaddr *sa; + int socklen; + struct evutil_addrinfo *res=NULL, *ai; + void *addrp; + + if (ent->h_addrtype == PF_INET) { memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(port); - memcpy(&sin.sin_addr, he->h_addr_list[0], 4); - sa_ptr = (struct sockaddr*)&sin; - slen = sizeof(struct sockaddr_in); - } else if (he->h_addrtype == AF_INET6) { - if (family != AF_INET6 && family != AF_UNSPEC) - return -1; + sa = (struct sockaddr *)&sin; + socklen = sizeof(struct sockaddr_in); + addrp = &sin.sin_addr; + if (ent->h_length != sizeof(sin.sin_addr)) { + event_warnx("Weird h_length from gethostbyname"); + return NULL; + } + } else if (ent->h_addrtype == PF_INET6) { + memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port); - memset(&sin6, 0, sizeof(sin6)); - memcpy(sin6.sin6_addr.s6_addr, &he->h_addr_list[1], 16); - sa_ptr = (struct sockaddr*)&sin6; - slen = sizeof(struct sockaddr_in6); + sa = (struct sockaddr *)&sin6; + socklen = sizeof(struct sockaddr_in); + addrp = &sin6.sin6_addr; + if (ent->h_length != sizeof(sin6.sin6_addr)) { + event_warnx("Weird h_length from gethostbyname"); + return NULL; + } + } else + return NULL; + + for (i = 0; ent->h_addr_list[i]; ++i) { + memcpy(addrp, ent->h_addr_list[i], ent->h_length); + ai = evutil_new_addrinfo(sa, socklen, hints); + if (!ai) { + evutil_freeaddrinfo(res); + return NULL; + } + res = evutil_addrinfo_append(res, ai); + } + + if (res && ((hints->ai_flags & EVUTIL_AI_CANONNAME) && ent->h_name)) + res->ai_canonname = mm_strdup(ent->h_name); + + return res; +} +#endif + +/* If the EVUTIL_AI_ADDRCONFIG flag is set on hints->ai_flags, and + * hints->ai_family is PF_UNSPEC, then revise the value of hints->ai_family so + * that we'll only get addresses we could maybe connect to. + */ +void +evutil_adjust_hints_for_addrconfig(struct evutil_addrinfo *hints) +{ + if (!(hints->ai_flags & EVUTIL_AI_ADDRCONFIG)) + return; + if (hints->ai_family != PF_UNSPEC) + return; + if (!have_checked_interfaces) + evutil_check_interfaces(0); + if (had_ipv4_address && !had_ipv6_address) { + hints->ai_family = PF_INET; + } else if (!had_ipv4_address && had_ipv6_address) { + hints->ai_family = PF_INET6; + } +} + +int +evutil_getaddrinfo(const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, struct evutil_addrinfo **res) +{ +#ifdef USE_NATIVE_GETADDRINFO +#if !defined(AI_ADDRCONFIG) || !defined(AI_NUMERICSERV) || defined(WIN32) + struct evutil_addrinfo hints; + if (hints_in) { + memcpy(&hints, hints_in, sizeof(hints)); + hints_in = &hints; + +#ifndef AI_ADDRCONFIG + /* Not every system has AI_ADDRCONFIG, so fake it. */ + if (hints.ai_family == PF_UNSPEC && + (hints.ai_flags & EVUTIL_AI_ADDRCONFIG)) { + evutil_adjust_hints_for_addrconfig(&hints); + } +#endif + +#ifndef AI_NUMERICSERV + /* Not every system has AI_NUMERICSERV, so fake it. */ + if (hints.ai_flags & EVUTIL_AI_NUMERICSERV) { + if (evutil_parse_servname(servname, + NULL, &hints) < 0) + return EVUTIL_EAI_NONAME; + } +#endif + +#ifdef WIN32 + /* Windows handles enough cases here weirdly enough that we + * are better off just overriding a bunch of them. */ + { + int err, port; + err = evutil_getaddrinfo_common(nodename,servname,&hints, + res, &port); + if (err != EVUTIL_EAI_NEED_RESOLVE) + return err; + } +#endif + } +#endif + + return getaddrinfo(nodename, servname, hints_in, res); +#else + int port=0, err; + struct hostent *ent = NULL; + struct evutil_addrinfo hints; + + if (hints_in) { + memcpy(&hints, hints_in, sizeof(hints)); } else { - event_warnx("gethostbyname returned unknown family %d", - he->h_addrtype); - return -1; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; } - if (slen > *socklen) { - return -1; + + evutil_adjust_hints_for_addrconfig(&hints); + + err = evutil_getaddrinfo_common(nodename, servname, &hints, res, &port); + if (err != EVUTIL_EAI_NEED_RESOLVE) { + /* We either succeeded or failed. No need to continue */ + return err; } - memcpy(sa, sa_ptr, slen); - *socklen = slen; + + err = 0; + /* Use any of the various gethostbyname_r variants as available. */ + { +#ifdef _EVENT_HAVE_GETHOSTBYNAME_R_6_ARG + /* This one is what glibc provides. */ + char buf[2048]; + struct hostent hostent; + int r; + r = gethostbyname_r(nodename, &hostent, buf, sizeof(buf), &ent, + &err); +#elif defined(_EVENT_HAVE_GETHOSTBYNAME_R_5_ARG) + char buf[2048]; + struct hostent hostent; + ent = gethostbyname_r(nodename, &hostent, buf, sizeof(buf), + &err); +#elif defined(_EVENT_HAVE_GETHOSTBYNAME_R_3_ARG) + struct hostent_data data; + struct hostent hostent; + memset(&data, 0, sizeof(data)); + err = gethostbyname_r(nodename, &hostent, &data); + ent = err ? NULL : &hostent; +#else + /* fall back to gethostbyname. */ + /* XXXX This needs a lock everywhere but Windows. */ + ent = gethostbyname(nodename); +#ifdef WIN32 + err = WSAGetLastError(); +#else + err = h_errno; +#endif +#endif + + /* Now we have either ent or err set. */ + if (!ent) { + /* XXX is this right for windows ? */ + switch (err) { + case TRY_AGAIN: + return EVUTIL_EAI_AGAIN; + case NO_RECOVERY: + default: + return EVUTIL_EAI_FAIL; + case HOST_NOT_FOUND: + return EVUTIL_EAI_NONAME; + case NO_ADDRESS: +#if NO_DATA != NO_ADDRESS + case NO_DATA: +#endif + return EVUTIL_EAI_NODATA; + } + } + + if (ent->h_addrtype != hints.ai_family && + hints.ai_family != PF_UNSPEC) { + /* This wasn't the type we were hoping for. Too bad + * we never had a chance to ask gethostbyname for what + * we wanted. */ + return EVUTIL_EAI_NONAME; + } + + /* Make sure we got _some_ answers. */ + if (ent->h_length == 0) + return EVUTIL_EAI_NODATA; + + /* If we got an address type we don't know how to make a + sockaddr for, give up. */ + if (ent->h_addrtype != PF_INET && ent->h_addrtype != PF_INET6) + return EVUTIL_EAI_FAMILY; + + *res = addrinfo_from_hostent(ent, port, &hints); + if (! *res) + return EVUTIL_EAI_MEMORY; + } + return 0; #endif } +void +evutil_freeaddrinfo(struct evutil_addrinfo *ai) +{ +#ifdef _EVENT_HAVE_GETADDRINFO + if (!(ai->ai_flags & EVUTIL_AI_LIBEVENT_ALLOCATED)) { + freeaddrinfo(ai); + return; + } +#endif + while (ai) { + struct evutil_addrinfo *next = ai->ai_next; + if (ai->ai_canonname) + mm_free(ai->ai_canonname); + mm_free(ai); + ai = next; + } +} + +static evdns_getaddrinfo_fn evdns_getaddrinfo_impl = NULL; + +void +evutil_set_evdns_getaddrinfo_fn(evdns_getaddrinfo_fn fn) +{ + if (!evdns_getaddrinfo_impl) + evdns_getaddrinfo_impl = fn; +} + +/* Internal helper function: act like evdns_getaddrinfo if dns_base is set; + * otherwise do a blocking resolve and pass the result to the callback in the + * way that evdns_getaddrinfo would. + */ +int +evutil_getaddrinfo_async(struct evdns_base *dns_base, + const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, + void (*cb)(int, struct evutil_addrinfo *, void *), void *arg) +{ + if (dns_base && evdns_getaddrinfo_impl) { + evdns_getaddrinfo_impl( + dns_base, nodename, servname, hints_in, cb, arg); + } else { + struct evutil_addrinfo *ai=NULL; + int err; + err = evutil_getaddrinfo(nodename, servname, hints_in, &ai); + cb(err, ai, arg); + } + return 0; +} + +const char * +evutil_gai_strerror(int err) +{ + switch (err) { + case EVUTIL_EAI_CANCEL: return "Request cancelled"; +#ifdef USE_NATIVE_GETADDRINFO + default: + return gai_strerror(err); +#else + case 0: return "No error"; + case EVUTIL_EAI_ADDRFAMILY: + return "address family for nodename not supported"; + case EVUTIL_EAI_AGAIN: + return "temporary failure in name resolution"; + case EVUTIL_EAI_BADFLAGS: + return "invalid value for ai_flags"; + case EVUTIL_EAI_FAIL: + return "non-recoverable failure in name resolution"; + case EVUTIL_EAI_FAMILY: + return "ai_family not supported"; + case EVUTIL_EAI_MEMORY: + return "memory allocation failure"; + case EVUTIL_EAI_NODATA: + return "no address associated with nodename"; + case EVUTIL_EAI_NONAME: + return "nodename nor servname provided, or not known"; + case EVUTIL_EAI_SERVICE: + return "servname not supported for ai_socktype"; + case EVUTIL_EAI_SOCKTYPE: + return "ai_socktype not supported"; + case EVUTIL_EAI_SYSTEM: return "system error"; + default: + return "Unknown error code"; +#endif + } +} + #ifdef WIN32 #define E(code, s) { code, (s " [" #code " ]") } static struct { int code; const char *msg; } windows_socket_errors[] = { diff --git a/http.c b/http.c index ae12c923..73716779 100644 --- a/http.c +++ b/http.c @@ -48,6 +48,8 @@ #include #include #include +#else +#include #endif #include @@ -100,8 +102,13 @@ #define NI_MAXSERV 32 #define NI_MAXHOST 1025 +#ifndef NI_NUMERICHOST #define NI_NUMERICHOST 1 +#endif + +#ifndef NI_NUMERICSERV #define NI_NUMERICSERV 2 +#endif static int fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, @@ -142,50 +149,6 @@ fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, #endif -#ifndef _EVENT_HAVE_GETADDRINFO -struct addrinfo { - int ai_family; - int ai_socktype; - int ai_protocol; - size_t ai_addrlen; - struct sockaddr *ai_addr; - struct addrinfo *ai_next; -}; -static int -fake_getaddrinfo(const char *hostname, struct addrinfo *ai) -{ - struct hostent *he = NULL; - struct sockaddr_in *sa; - if (hostname) { - he = gethostbyname(hostname); - if (!he) - return (-1); - } - ai->ai_family = he ? he->h_addrtype : AF_INET; - ai->ai_socktype = SOCK_STREAM; - ai->ai_protocol = 0; - ai->ai_addrlen = sizeof(struct sockaddr_in); - if (NULL == (ai->ai_addr = mm_malloc(ai->ai_addrlen))) - return (-1); - sa = (struct sockaddr_in*)ai->ai_addr; - memset(sa, 0, ai->ai_addrlen); - if (he) { - sa->sin_family = he->h_addrtype; - memcpy(&sa->sin_addr, he->h_addr_list[0], he->h_length); - } else { - sa->sin_family = AF_INET; - sa->sin_addr.s_addr = INADDR_ANY; - } - ai->ai_next = NULL; - return (0); -} -static void -fake_freeaddrinfo(struct addrinfo *ai) -{ - mm_free(ai->ai_addr); -} -#endif - #ifndef MIN #define MIN(a,b) (((a)<(b))?(a):(b)) #endif @@ -193,7 +156,7 @@ fake_freeaddrinfo(struct addrinfo *ai) extern int debug; static int socket_connect(evutil_socket_t kefd, const char *address, unsigned short port); -static evutil_socket_t bind_socket_ai(struct addrinfo *, int reuse); +static evutil_socket_t bind_socket_ai(struct evutil_addrinfo *, int reuse); static evutil_socket_t bind_socket(const char *, ev_uint16_t, int reuse); static void name_from_addr(struct sockaddr *, ev_socklen_t, char **, char **); static int evhttp_associate_new_request_with_connection( @@ -2979,32 +2942,6 @@ evhttp_get_request(struct evhttp *http, evutil_socket_t fd, * Network helper functions that we do not want to export to the rest of * the world. */ -#if 0 /* Unused */ -static struct addrinfo * -addr_from_name(char *address) -{ -#ifdef _EVENT_HAVE_GETADDRINFO - struct addrinfo ai, *aitop; - int ai_result; - - memset(&ai, 0, sizeof(ai)); - ai.ai_family = AF_INET; - ai.ai_socktype = SOCK_RAW; - ai.ai_flags = 0; - if ((ai_result = getaddrinfo(address, NULL, &ai, &aitop)) != 0) { - if ( ai_result == EAI_SYSTEM ) - event_warn("getaddrinfo"); - else - event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); - } - - return (aitop); -#else - EVUTIL_ASSERT(0); - return NULL; /* XXXXX Use gethostbyname, if this function is ever used. */ -#endif -} -#endif static void name_from_addr(struct sockaddr *sa, ev_socklen_t salen, @@ -3020,9 +2957,12 @@ name_from_addr(struct sockaddr *sa, ev_socklen_t salen, NI_NUMERICHOST|NI_NUMERICSERV); if (ni_result != 0) { +#ifdef EAI_SYSTEM + /* Windows doesn't have an EAI_SYSTEM. */ if (ni_result == EAI_SYSTEM) event_err(1, "getnameinfo failed"); else +#endif event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result)); return; } @@ -3041,7 +2981,7 @@ name_from_addr(struct sockaddr *sa, ev_socklen_t salen, /* Create a non-blocking socket and bind it */ /* todo: rename this function */ static evutil_socket_t -bind_socket_ai(struct addrinfo *ai, int reuse) +bind_socket_ai(struct evutil_addrinfo *ai, int reuse) { evutil_socket_t fd; @@ -3084,49 +3024,40 @@ bind_socket_ai(struct addrinfo *ai, int reuse) return (-1); } -static struct addrinfo * +static struct evutil_addrinfo * make_addrinfo(const char *address, ev_uint16_t port) { - struct addrinfo *aitop = NULL; + struct evutil_addrinfo *ai = NULL; -#ifdef _EVENT_HAVE_GETADDRINFO - struct addrinfo ai; + struct evutil_addrinfo hints; char strport[NI_MAXSERV]; int ai_result; - memset(&ai, 0, sizeof(ai)); - ai.ai_family = AF_UNSPEC; - ai.ai_socktype = SOCK_STREAM; - ai.ai_flags = AI_PASSIVE; /* turn NULL host name into INADDR_ANY */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + /* turn NULL hostname into INADDR_ANY, and skip looking up any address + * types we don't have an interface to connect to. */ + hints.ai_flags = EVUTIL_AI_PASSIVE|EVUTIL_AI_ADDRCONFIG; evutil_snprintf(strport, sizeof(strport), "%d", port); - if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) { - if ( ai_result == EAI_SYSTEM ) + if ((ai_result = evutil_getaddrinfo(address, strport, &hints, &ai)) + != 0) { + if (ai_result == EVUTIL_EAI_SYSTEM) event_warn("getaddrinfo"); else - event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + event_warnx("getaddrinfo: %s", + evutil_gai_strerror(ai_result)); return (NULL); } -#else - static int cur; - static struct addrinfo ai[2]; /* We will be returning the address of some of this memory so it has to last even after this call. */ - if (++cur == 2) cur = 0; /* allow calling this function twice */ - if (fake_getaddrinfo(address, &ai[cur]) < 0) { - event_warn("fake_getaddrinfo"); - return (NULL); - } - aitop = &ai[cur]; - ((struct sockaddr_in *) aitop->ai_addr)->sin_port = htons(port); -#endif - - return (aitop); + return (ai); } static evutil_socket_t bind_socket(const char *address, ev_uint16_t port, int reuse) { evutil_socket_t fd; - struct addrinfo *aitop = NULL; + struct evutil_addrinfo *aitop = NULL; /* just create an unbound socket */ if (address == NULL && port == 0) @@ -3139,11 +3070,7 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) fd = bind_socket_ai(aitop, reuse); -#ifdef _EVENT_HAVE_GETADDRINFO - freeaddrinfo(aitop); -#else - fake_freeaddrinfo(aitop); -#endif + evutil_freeaddrinfo(aitop); return (fd); } @@ -3151,7 +3078,7 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) static int socket_connect(evutil_socket_t fd, const char *address, unsigned short port) { - struct addrinfo *ai = make_addrinfo(address, port); + struct evutil_addrinfo *ai = make_addrinfo(address, port); int res = -1; if (ai == NULL) { @@ -3170,11 +3097,7 @@ socket_connect(evutil_socket_t fd, const char *address, unsigned short port) res = 0; out: -#ifdef _EVENT_HAVE_GETADDRINFO - freeaddrinfo(ai); -#else - fake_freeaddrinfo(ai); -#endif + evutil_freeaddrinfo(ai); return (res); } diff --git a/include/event2/dns.h b/include/event2/dns.h index f05f71d1..233cbf6d 100644 --- a/include/event2/dns.h +++ b/include/event2/dns.h @@ -597,6 +597,36 @@ struct sockaddr; */ int evdns_server_request_get_requesting_addr(struct evdns_server_request *_req, struct sockaddr *sa, int addr_len); +/** Callback for evdns_getaddrinfo. */ +typedef void (*evdns_getaddrinfo_cb)(int result, struct evutil_addrinfo *res, void *arg); + +struct evdns_base; +struct evdns_getaddrinfo_request; +/** Make a non-blocking getaddrinfo request using the dns_base in 'dns_base'. + * + * If we can answer the request immediately (with an error or not!), then we + * invoke cb immediately and return NULL. Otherwise we return + * an evdns_getaddrinfo_request and invoke cb later. + * + * When the callback is invoked, we pass as its first argument the error code + * that getaddrinfo would return (or 0 for no error). As its second argument, + * we pass the evutil_addrinfo structures we found (or NULL on error). We + * pass 'arg' as the third argument. + * + * - The AI_V4MAPPED and AI_ALL flags are not currently implemented. + * - We don't look at the /etc/hosts file. + */ +struct evdns_getaddrinfo_request *evdns_getaddrinfo( + struct evdns_base *dns_base, + const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, + evdns_getaddrinfo_cb cb, void *arg); + +/* Cancel an in-progress evdns_getaddrinfo. This MUST NOT be called after the + * getaddrinfo's callback has been invoked. The resolves will be cancelled, + * and the callback will be invoked with the error EVUTIL_EAI_CANCEL. */ +void evdns_getaddrinfo_cancel(struct evdns_getaddrinfo_request *req); + #ifdef __cplusplus } #endif diff --git a/include/event2/dns_compat.h b/include/event2/dns_compat.h index 1e3c410c..d9d2a255 100644 --- a/include/event2/dns_compat.h +++ b/include/event2/dns_compat.h @@ -67,6 +67,16 @@ extern "C" { */ int evdns_init(void); +struct evdns_base; +/** + Return the global evdns_base created by event_init() and used by the other + deprecated functions. + + @deprecated This function is deprecated because use of the global + evdns_base is error-prone. + */ +struct evdns_base *evdns_get_global_base(void); + /** Shut down the asynchronous DNS resolver and terminate all active requests. diff --git a/include/event2/util.h b/include/event2/util.h index b2b9a8ab..d96c231b 100644 --- a/include/event2/util.h +++ b/include/event2/util.h @@ -56,6 +56,10 @@ extern "C" { #include #endif #include +#ifdef _EVENT_HAVE_NETDB_H +#define _GNU_SOURCE +#include +#endif /* Integer type definitions for types that are supposed to be defined in the * C99-specified stdint.h. Shamefully, some platforms do not include @@ -324,6 +328,138 @@ int evutil_ascii_strcasecmp(const char *str1, const char *str2); */ int evutil_ascii_strncasecmp(const char *str1, const char *str2, size_t n); +/* Here we define evutil_addrinfo to the native addrinfo type, or redefinte it + * if this system has no getaddrinfo(). */ +#ifdef _EVENT_HAVE_STRUCT_ADDRINFO +#define evutil_addrinfo addrinfo +#else +struct evutil_addrinfo { + int ai_flags; /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for nodename */ + struct sockaddr *ai_addr; /* binary address */ + struct evutil_addrinfo *ai_next; /* next structure in linked list */ +}; +#endif +#ifdef EAI_ADDRFAMILY +#define EVUTIL_EAI_ADDRFAMILY EAI_ADDRFAMILY +#else +#define EVUTIL_EAI_ADDRFAMILY -901 +#endif +#ifdef EAI_AGAIN +#define EVUTIL_EAI_AGAIN EAI_AGAIN +#else +#define EVUTIL_EAI_AGAIN -902 +#endif +#ifdef EAI_BADFLAGS +#define EVUTIL_EAI_BADFLAGS EAI_BADFLAGS +#else +#define EVUTIL_EAI_BADFLAGS -903 +#endif +#ifdef EAI_FAIL +#define EVUTIL_EAI_FAIL EAI_FAIL +#else +#define EVUTIL_EAI_FAIL -904 +#endif +#ifdef EAI_FAMILY +#define EVUTIL_EAI_FAMILY EAI_FAMILY +#else +#define EVUTIL_EAI_FAMILY -905 +#endif +#ifdef EAI_MEMORY +#define EVUTIL_EAI_MEMORY EAI_MEMORY +#else +#define EVUTIL_EAI_MEMORY -906 +#endif +/* This test is a bit complicated, since some MS SDKs decide to + * remove NODATA or redefine it to be the same as NONAME, in a + * fun interpretation of RFC 2553 and RFC 3493. */ +#if defined(EAI_NODATA) && (!defined(EAI_NONAME) || EAI_NODATA != EAI_NONAME) +#define EVUTIL_EAI_NODATA EAI_NODATA +#else +#define EVUTIL_EAI_NODATA -907 +#endif +#ifdef EAI_NONAME +#define EVUTIL_EAI_NONAME EAI_NONAME +#else +#define EVUTIL_EAI_NONAME -908 +#endif +#ifdef EAI_SERVICE +#define EVUTIL_EAI_SERVICE EAI_SERVICE +#else +#define EVUTIL_EAI_SERVICE -909 +#endif +#ifdef EAI_SOCKTYPE +#define EVUTIL_EAI_SOCKTYPE EAI_SOCKTYPE +#else +#define EVUTIL_EAI_SOCKTYPE -910 +#endif +#ifdef EAI_SYSTEM +#define EVUTIL_EAI_SYSTEM EAI_SYSTEM +#else +#define EVUTIL_EAI_SYSTEM -911 +#endif + +#define EVUTIL_EAI_CANCEL -90001 + +#ifdef AI_PASSIVE +#define EVUTIL_AI_PASSIVE AI_PASSIVE +#else +#define EVUTIL_AI_PASSIVE 0x1000 +#endif +#ifdef AI_CANONNAME +#define EVUTIL_AI_CANONNAME AI_CANONNAME +#else +#define EVUTIL_AI_CANONNAME 0x2000 +#endif +#ifdef AI_NUMERICHOST +#define EVUTIL_AI_NUMERICHOST AI_NUMERICHOST +#else +#define EVUTIL_AI_NUMERICHOST 0x4000 +#endif +#ifdef AI_NUMERICSERV +#define EVUTIL_AI_NUMERICSERV AI_NUMERICSERV +#else +#define EVUTIL_AI_NUMERICSERV 0x8000 +#endif +#ifdef AI_V4MAPPED +#define EVUTIL_AI_V4MAPPED AI_V4MAPPED +#else +#define EVUTIL_AI_V4MAPPED 0x10000 +#endif +#ifdef AI_ALL +#define EVUTIL_AI_ALL AI_ALL +#else +#define EVUTIL_AI_ALL 0x20000 +#endif +#ifdef AI_ADDRCONFIG +#define EVUTIL_AI_ADDRCONFIG AI_ADDRCONFIG +#else +#define EVUTIL_AI_ADDRCONFIG 0x40000 +#endif + +struct evutil_addrinfo; +/* This function clones getaddrinfo for systems that don't have it. For full + * details, see RFC 3493, section 6.1. + * + * Limitations: + * - When the system has no getaddrinfo, we fall back to gethostbyname_r or + * gethostbyname, with their attendant issues. + * - The AI_V4MAPPED and AI_ALL flags are not currently implemented. + * + * For a nonblocking variant, see evdns_getaddrinfo. + */ +int evutil_getaddrinfo(const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, struct evutil_addrinfo **res); + +/* Release storage allocated by evutil_getaddrinfo or evdns_getaddrinfo. */ +void evutil_freeaddrinfo(struct evutil_addrinfo *ai); + +const char *evutil_gai_strerror(int err); + #ifdef __cplusplus } #endif diff --git a/sample/dns-example.c b/sample/dns-example.c index 7757dd48..fda7def0 100644 --- a/sample/dns-example.c +++ b/sample/dns-example.c @@ -57,6 +57,37 @@ main_callback(int result, char type, int count, int ttl, fflush(stdout); } +static void +gai_callback(int err, struct evutil_addrinfo *ai, void *arg) +{ + const char *name = arg; + struct evutil_addrinfo *ai_first = NULL; + int i; + if (err) { + printf("%s: %s\n", name, evutil_gai_strerror(err)); + } + if (ai && ai->ai_canonname) + printf(" %s ==> %s\n", name, ai->ai_canonname); + for (i=0; ai; ai = ai->ai_next, ++i) { + char buf[128]; + if (ai->ai_family == PF_INET) { + struct sockaddr_in *sin = + (struct sockaddr_in*)ai->ai_addr; + evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, + sizeof(buf)); + printf("[%d] %s: %s\n",i,name,buf); + } else { + struct sockaddr_in6 *sin6 = + (struct sockaddr_in6*)ai->ai_addr; + evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, + sizeof(buf)); + printf("[%d] %s: %s\n",i,name,buf); + } + } + if (ai_first) + evutil_freeaddrinfo(ai_first); +} + static void evdns_server_callback(struct evdns_server_request *req, void *data) { @@ -101,7 +132,7 @@ logfn(int is_warn, const char *msg) { int main(int c, char **v) { int idx; - int reverse = 0, servertest = 0; + int reverse = 0, servertest = 0, use_getaddrinfo = 0; struct event_base *event_base = NULL; struct evdns_base *evdns_base = NULL; if (c<2) { @@ -115,6 +146,8 @@ main(int c, char **v) { reverse = 1; else if (!strcmp(v[idx], "-v")) verbose = 1; + else if (!strcmp(v[idx], "-g")) + use_getaddrinfo = 1; else if (!strcmp(v[idx], "-servertest")) servertest = 1; else @@ -148,15 +181,26 @@ main(int c, char **v) { "/etc/resolv.conf"); #endif } + + printf("EVUTIL_AI_CANONNAME in example = %d\n", EVUTIL_AI_CANONNAME); for (; idx < c; ++idx) { if (reverse) { struct in_addr addr; - if (!inet_aton(v[idx], &addr)) { + if (evutil_inet_pton(AF_INET, v[idx], &addr)!=1) { fprintf(stderr, "Skipping non-IP %s\n", v[idx]); continue; } fprintf(stderr, "resolving %s...\n",v[idx]); evdns_base_resolve_reverse(evdns_base, &addr, 0, main_callback, v[idx]); + } else if (use_getaddrinfo) { + struct evutil_addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = EVUTIL_AI_CANONNAME; + fprintf(stderr, "resolving (fwd) %s...\n",v[idx]); + evdns_getaddrinfo(evdns_base, v[idx], NULL, &hints, + gai_callback, v[idx]); } else { fprintf(stderr, "resolving (fwd) %s...\n",v[idx]); evdns_base_resolve_ipv4(evdns_base, v[idx], 0, main_callback, v[idx]); diff --git a/test/regress.h b/test/regress.h index 0593a69b..be69a58e 100644 --- a/test/regress.h +++ b/test/regress.h @@ -97,6 +97,17 @@ void run_legacy_test_fn(void *ptr); { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ test_## name } +struct evutil_addrinfo; +struct evutil_addrinfo *ai_find_by_family(struct evutil_addrinfo *ai, int f); +struct evutil_addrinfo *ai_find_by_protocol(struct evutil_addrinfo *ai, int p); +int _test_ai_eq(const struct evutil_addrinfo *ai, const char *sockaddr_port, + int socktype, int protocol, int line); + +#define test_ai_eq(ai, str, s, p) do { \ + if (_test_ai_eq((ai), (str), (s), (p), __LINE__)<0) \ + goto end; \ + } while(0) + #ifdef __cplusplus } diff --git a/test/regress_dns.c b/test/regress_dns.c index d6f6783b..b7059c05 100644 --- a/test/regress_dns.c +++ b/test/regress_dns.c @@ -71,6 +71,8 @@ static int dns_ok = 0; static int dns_got_cancel = 0; static int dns_err = 0; +static int get_socket_port(evutil_socket_t fd); + static void dns_gethostbyname_cb(int result, char type, int count, int ttl, void *addresses, void *arg) @@ -480,7 +482,7 @@ end: static struct evdns_server_port * get_generic_server(struct event_base *base, - ev_uint16_t portnum, + ev_uint16_t *portnum, evdns_request_callback_fn_type cb, void *arg) { @@ -497,12 +499,14 @@ get_generic_server(struct event_base *base, memset(&my_addr, 0, sizeof(my_addr)); my_addr.sin_family = AF_INET; - my_addr.sin_port = htons(portnum); + my_addr.sin_port = htons(*portnum); my_addr.sin_addr.s_addr = htonl(0x7f000001UL); if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr)) < 0) { tt_abort_perror("bind"); } port = evdns_add_server_port_with_base(base, sock, 0, cb, arg); + if (!*portnum) + *portnum = get_socket_port(sock); return port; end: @@ -571,10 +575,11 @@ dns_search_test(void *arg) struct event_base *base = data->base; struct evdns_server_port *port = NULL; struct evdns_base *dns = NULL; + ev_uint16_t portnum = 53900;/*XXXX let the code pick a port*/ struct generic_dns_callback_result r1, r2, r3, r4, r5; - port = get_generic_server(base, 53900, generic_dns_server_cb, + port = get_generic_server(base, &portnum, generic_dns_server_cb, search_table); tt_assert(port); @@ -655,10 +660,11 @@ dns_retry_test(void *arg) struct evdns_server_port *port = NULL; struct evdns_base *dns = NULL; int drop_count = 2; + ev_uint16_t portnum = 53900;/*XXXX let the code pick a port*/ struct generic_dns_callback_result r1; - port = get_generic_server(base, 53900, fail_server_cb, + port = get_generic_server(base, &portnum, fail_server_cb, &drop_count); tt_assert(port); @@ -743,11 +749,12 @@ dns_reissue_test(void *arg) struct evdns_server_port *port1 = NULL, *port2 = NULL; struct evdns_base *dns = NULL; struct generic_dns_callback_result r1; + ev_uint16_t portnum1 = 53900, portnum2=53901; - port1 = get_generic_server(base, 53900, generic_dns_server_cb, + port1 = get_generic_server(base, &portnum1, generic_dns_server_cb, internal_error_table); tt_assert(port1); - port2 = get_generic_server(base, 53901, generic_dns_server_cb, + port2 = get_generic_server(base, &portnum2, generic_dns_server_cb, reissue_table); tt_assert(port2); @@ -802,11 +809,12 @@ dns_inflight_test(void *arg) struct event_base *base = data->base; struct evdns_server_port *port = NULL; struct evdns_base *dns = NULL; + ev_uint16_t portnum = 53900;/*XXXX let the code pick a port*/ struct generic_dns_callback_result r[20]; int i; - port = get_generic_server(base, 53900, generic_dns_server_cb, + port = get_generic_server(base, &portnum, generic_dns_server_cb, reissue_table); tt_assert(port); @@ -845,9 +853,10 @@ end: static int total_connected_or_failed = 0; static struct event_base *be_connect_hostname_base = NULL; -/* Implements a DNS server for the connect_hostname test. */ +/* Implements a DNS server for the connect_hostname test and the + * getaddrinfo_async test */ static void -be_connect_hostname_server_cb(struct evdns_server_request *req, void *data) +be_getaddrinfo_server_cb(struct evdns_server_request *req, void *data) { int i; int *n_got_p=data; @@ -859,6 +868,8 @@ be_connect_hostname_server_cb(struct evdns_server_request *req, void *data) const int qclass = req->questions[i]->dns_question_class; const char *qname = req->questions[i]->name; struct in_addr ans; + struct in6_addr ans6; + memset(&ans6, 0, sizeof(ans6)); if (qtype == EVDNS_TYPE_A && qclass == EVDNS_CLASS_INET && @@ -870,6 +881,72 @@ be_connect_hostname_server_cb(struct evdns_server_request *req, void *data) } else if (!evutil_ascii_strcasecmp(qname, "nosuchplace.example.com")) { /* ok, just say notfound. */ + } else if (!evutil_ascii_strcasecmp(qname, + "both.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0x50502020); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x80; + ans6.s6_addr[1] = 0xff; + ans6.s6_addr[14] = 0xbb; + ans6.s6_addr[15] = 0xbb; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } + evdns_server_request_add_cname_reply(req, qname, + "both-canonical.example.com", 1000); + } else if (!evutil_ascii_strcasecmp(qname, + "v4only.example.com") || + !evutil_ascii_strcasecmp(qname, "v4assert.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0x12345678); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "v4assert.example.com")) { + TT_FAIL(("Got an AAAA request for v4assert")); + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6only.example.com") || + !evutil_ascii_strcasecmp(qname, "v6assert.example.com")) { + if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x0b; + ans6.s6_addr[1] = 0x0b; + ans6.s6_addr[14] = 0xf0; + ans6.s6_addr[15] = 0x0d; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "v6assert.example.com")) { + TT_FAIL(("Got a A request for v6assert")); + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6timeout.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0xabcdef01); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_AAAA) { + /* Let the v6 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6timeout-nonexist.example.com")) { + if (qtype == EVDNS_TYPE_A) { + /* Fall through, give an nexist. */ + } else if (qtype == EVDNS_TYPE_AAAA) { + /* Let the v6 request time out.*/ + evdns_server_request_drop(req); + return; + } } else { TT_GRIPE(("Got weird request for %s",qname)); } @@ -918,6 +995,7 @@ be_connect_hostname_event_cb(struct bufferevent *bev, short what, void *ctx) if ((what & BEV_EVENT_CONNECTED) || (what & BEV_EVENT_ERROR)) { ++total_connected_or_failed; + TT_BLATHER(("Got %d connections or errors.", total_connected_or_failed)); if (total_connected_or_failed >= 5) event_base_loopexit(be_connect_hostname_base, NULL); @@ -940,7 +1018,8 @@ test_bufferevent_connect_hostname(void *arg) struct evdns_server_port *port=NULL; evutil_socket_t server_fd=-1; struct sockaddr_in sin; - int listener_port=-1, dns_port=-1; + int listener_port=-1; + ev_uint16_t dns_port=0; int n_accept=0, n_dns=0; char buf[128]; @@ -957,6 +1036,7 @@ test_bufferevent_connect_hostname(void *arg) -1, (struct sockaddr *)&sin, sizeof(sin)); listener_port = get_socket_port(evconnlistener_get_fd(listener)); +#if 0 /* Start an evdns server that resolves nobodaddy.example.com to * 127.0.0.1 */ memset(&sin, 0, sizeof(sin)); @@ -971,7 +1051,12 @@ test_bufferevent_connect_hostname(void *arg) evutil_make_socket_nonblocking(server_fd); dns_port = get_socket_port(server_fd); port = evdns_add_server_port_with_base(data->base, server_fd, 0, - be_connect_hostname_server_cb, &n_dns); + be_getaddrinfo_server_cb, &n_dns); +#endif + port = get_generic_server(data->base, &dns_port, + be_getaddrinfo_server_cb, &n_dns); + tt_assert(port); + tt_int_op(dns_port, >=, 0); /* Start an evdns_base that uses the server as its resolver. */ dns = evdns_base_new(data->base, 0); @@ -1012,8 +1097,8 @@ test_bufferevent_connect_hostname(void *arg) tt_assert(!bufferevent_socket_connect_hostname(be4, NULL, AF_INET, "localhost", listener_port)); /* Use the blocking resolver with a nonexistent hostname. */ - tt_assert(bufferevent_socket_connect_hostname(be5, NULL, AF_INET, - "nonesuch.nowhere.example.com", 80) < 0); + tt_assert(!bufferevent_socket_connect_hostname(be5, NULL, AF_INET, + "nonesuch.nowhere.example.com", 80)); event_base_dispatch(data->base); @@ -1047,6 +1132,326 @@ end: bufferevent_free(be5); } + +struct gai_outcome { + int err; + struct evutil_addrinfo *ai; +}; + +static int n_gai_results_pending = 0; +static struct event_base *exit_base_on_no_pending_results = NULL; + +static void +gai_cb(int err, struct evutil_addrinfo *res, void *ptr) +{ + struct gai_outcome *go = ptr; + go->err = err; + go->ai = res; + if (--n_gai_results_pending <= 0 && exit_base_on_no_pending_results) + event_base_loopexit(exit_base_on_no_pending_results, NULL); + if (n_gai_results_pending < 900) + TT_BLATHER(("Got an answer; expecting %d more.", + n_gai_results_pending)); +} + +static void +test_getaddrinfo_async(void *arg) +{ + struct basic_test_data *data = arg; + struct evutil_addrinfo hints, *a; + struct gai_outcome local_outcome; + struct gai_outcome a_out[10]; + int i; + struct evdns_getaddrinfo_request *r; + char buf[128]; + struct evdns_server_port *port = NULL; + ev_uint16_t dns_port = 0; + int n_dns_questions = 0; + + struct evdns_base *dns_base = evdns_base_new(data->base, 0); + + memset(a_out, 0, sizeof(a_out)); + + n_gai_results_pending = 10000; /* don't think about exiting yet. */ + + /* 1. Try some cases that will never hit the asynchronous resolver. */ + /* 1a. Simple case with a symbolic service name */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + memset(&local_outcome, 0, sizeof(local_outcome)); + r = evdns_getaddrinfo(dns_base, "1.2.3.4", "http", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_ptr_op(local_outcome.ai,!=,NULL); + test_ai_eq(local_outcome.ai, "1.2.3.4:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1b. EVUTIL_AI_NUMERICHOST is set */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + memset(&local_outcome, 0, sizeof(local_outcome)); + r = evdns_getaddrinfo(dns_base, "www.google.com", "80", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,EVUTIL_EAI_NONAME); + tt_ptr_op(local_outcome.ai,==,NULL); + + /* 1c. We give a numeric address (ipv6) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + r = evdns_getaddrinfo(dns_base, "f::f", "8008", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + tt_ptr_op(local_outcome.ai->ai_next,==,NULL); + test_ai_eq(local_outcome.ai, "[f::f]:8008", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1d. We give a numeric address (ipv4) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "5.6.7.8", NULL, + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + a = ai_find_by_protocol(local_outcome.ai, IPPROTO_TCP); + tt_assert(a); + test_ai_eq(a, "5.6.7.8", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_protocol(local_outcome.ai, IPPROTO_UDP); + tt_assert(a); + test_ai_eq(a, "5.6.7.8", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1e. nodename is NULL (bind) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = EVUTIL_AI_PASSIVE; + r = evdns_getaddrinfo(dns_base, NULL, "9090", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 0.0.0.0... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "0.0.0.0:9090", SOCK_DGRAM, IPPROTO_UDP); + /* ... and a v6 address of ::0 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::]:9090", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1f. nodename is NULL (connect) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, NULL, "2", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 127.0.0.1 .... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "127.0.0.1:2", SOCK_STREAM, IPPROTO_TCP); + /* ... and a v6 address of ::1 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::1]:2", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 2. Okay, now we can actually test the asynchronous resolver. */ + /* Start a dummy local dns server... */ + port = get_generic_server(data->base, &dns_port, + be_getaddrinfo_server_cb, &n_dns_questions); + tt_assert(port); + tt_int_op(dns_port, >=, 0); + /* ... and tell the evdns_base about it. */ + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", dns_port); + evdns_base_nameserver_ip_add(dns_base, buf); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_CANONNAME; + /* 0: Request for both.example.com should return both addresses. */ + r = evdns_getaddrinfo(dns_base, "both.example.com", "8000", + &hints, gai_cb, &a_out[0]); + tt_assert(r); + + /* 1: Request for v4only.example.com should return one address. */ + r = evdns_getaddrinfo(dns_base, "v4only.example.com", "8001", + &hints, gai_cb, &a_out[1]); + tt_assert(r); + + /* 2: Request for v6only.example.com should return one address. */ + hints.ai_flags = 0; + r = evdns_getaddrinfo(dns_base, "v6only.example.com", "8002", + &hints, gai_cb, &a_out[2]); + tt_assert(r); + + /* 3: PF_INET request for v4assert.example.com should not generate a + * v6 request. The server will fail the test if it does. */ + hints.ai_family = PF_INET; + r = evdns_getaddrinfo(dns_base, "v4assert.example.com", "8003", + &hints, gai_cb, &a_out[3]); + tt_assert(r); + + /* 4: PF_INET6 request for v6assert.example.com should not generate a + * v4 request. The server will fail the test if it does. */ + hints.ai_family = PF_INET6; + r = evdns_getaddrinfo(dns_base, "v6assert.example.com", "8004", + &hints, gai_cb, &a_out[4]); + tt_assert(r); + + /* 5: PF_INET request for nosuchplace.example.com should give NEXIST. */ + hints.ai_family = PF_INET; + r = evdns_getaddrinfo(dns_base, "nosuchplace.example.com", "8005", + &hints, gai_cb, &a_out[5]); + tt_assert(r); + + /* 6: PF_UNSPEC request for nosuchplace.example.com should give NEXIST. + */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "nosuchplace.example.com", "8006", + &hints, gai_cb, &a_out[6]); + tt_assert(r); + + /* 7: PF_UNSPEC request for v6timeout.example.com should give an ipv4 + * address only. */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "v6timeout.example.com", "8007", + &hints, gai_cb, &a_out[7]); + tt_assert(r); + + /* 8: PF_UNSPEC request for v6timeout-nonexist.example.com should give + * a NEXIST */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "v6timeout-nonexist.example.com", + "8008", &hints, gai_cb, &a_out[8]); + tt_assert(r); + + /* 9: AI_ADDRCONFIG should at least not crash. Can't test it more + * without knowing what kind of internet we have. */ + hints.ai_flags |= EVUTIL_AI_ADDRCONFIG; + r = evdns_getaddrinfo(dns_base, "both.example.com", + "8009", &hints, gai_cb, &a_out[9]); + tt_assert(r); + + /* XXXXX There are more tests we could do, including: + + - A test to elicit NODATA. + - A test of cancelling a request. + + */ + + n_gai_results_pending = 10; + exit_base_on_no_pending_results = data->base; + + event_base_dispatch(data->base); + + /* 0: both.example.com */ + tt_int_op(a_out[0].err, ==, 0); + tt_assert(a_out[0].ai); + tt_assert(a_out[0].ai->ai_next); + tt_assert(!a_out[0].ai->ai_next->ai_next); + a = ai_find_by_family(a_out[0].ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "80.80.32.32:8000", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_family(a_out[0].ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[80ff::bbbb]:8000", SOCK_STREAM, IPPROTO_TCP); + tt_assert(a_out[0].ai->ai_canonname); + tt_str_op(a_out[0].ai->ai_canonname, ==, "both-canonical.example.com"); + + /* 1: v4only.example.com */ + tt_int_op(a_out[1].err, ==, 0); + tt_assert(a_out[1].ai); + tt_assert(! a_out[1].ai->ai_next); + test_ai_eq(a_out[1].ai, "18.52.86.120:8001", SOCK_STREAM, IPPROTO_TCP); + tt_assert(a_out[1].ai->ai_canonname == NULL); + + + /* 2: v6only.example.com */ + tt_int_op(a_out[2].err, ==, 0); + tt_assert(a_out[2].ai); + tt_assert(! a_out[2].ai->ai_next); + test_ai_eq(a_out[2].ai, "[b0b::f00d]:8002", SOCK_STREAM, IPPROTO_TCP); + + /* 3: v4assert.example.com */ + tt_int_op(a_out[3].err, ==, 0); + tt_assert(a_out[3].ai); + tt_assert(! a_out[3].ai->ai_next); + test_ai_eq(a_out[3].ai, "18.52.86.120:8003", SOCK_STREAM, IPPROTO_TCP); + + /* 4: v6assert.example.com */ + tt_int_op(a_out[4].err, ==, 0); + tt_assert(a_out[4].ai); + tt_assert(! a_out[4].ai->ai_next); + test_ai_eq(a_out[4].ai, "[b0b::f00d]:8004", SOCK_STREAM, IPPROTO_TCP); + + /* 5: nosuchplace.example.com (inet) */ + tt_int_op(a_out[5].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[5].ai); + + /* 6: nosuchplace.example.com (unspec) */ + tt_int_op(a_out[6].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[6].ai); + + /* 7: v6timeout.example.com */ + tt_int_op(a_out[7].err, ==, 0); + tt_assert(a_out[7].ai); + tt_assert(! a_out[7].ai->ai_next); + test_ai_eq(a_out[7].ai, "171.205.239.1:8007", SOCK_STREAM, IPPROTO_TCP); + + /* 8: v6timeout-nonexist.example.com */ + tt_int_op(a_out[8].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[8].ai); + + /* 9: both (ADDRCONFIG) */ + tt_int_op(a_out[9].err, ==, 0); + tt_assert(a_out[9].ai); + a = ai_find_by_family(a_out[9].ai, PF_INET); + if (a) + test_ai_eq(a, "80.80.32.32:8009", SOCK_STREAM, IPPROTO_TCP); + else + tt_assert(ai_find_by_family(a_out[9].ai, PF_INET6)); + a = ai_find_by_family(a_out[9].ai, PF_INET6); + if (a) + test_ai_eq(a, "[80ff::bbbb]:8009", SOCK_STREAM, IPPROTO_TCP); + else + tt_assert(ai_find_by_family(a_out[9].ai, PF_INET)); + +end: + if (local_outcome.ai) + evutil_freeaddrinfo(local_outcome.ai); + for (i=0;i<10;++i) { + if (a_out[i].ai) + evutil_freeaddrinfo(a_out[i].ai); + } + if (port) + evdns_close_server_port(port); + if (dns_base) + evdns_base_free(dns_base, 0); +} + + #define DNS_LEGACY(name, flags) \ { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ dns_##name } @@ -1064,6 +1469,9 @@ struct testcase_t dns_testcases[] = { { "bufferevent_connnect_hostname", test_bufferevent_connect_hostname, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "getaddrinfo_async", test_getaddrinfo_async, + TT_FORK|TT_NEED_BASE, &basic_setup, (char*)"" }, + END_OF_TESTCASES }; diff --git a/test/regress_http.c b/test/regress_http.c index 6bff6af7..6162f604 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -2042,7 +2042,6 @@ http_connection_retry_test(void) if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) { tt_abort_msg("Couldn't make request"); - } /* start up a web server one second after the connection tried diff --git a/test/regress_util.c b/test/regress_util.c index 49cefa02..e474a34a 100644 --- a/test/regress_util.c +++ b/test/regress_util.c @@ -489,51 +489,267 @@ end: } -static void -test_evutil_resolve(void *arg) +struct evutil_addrinfo * +ai_find_by_family(struct evutil_addrinfo *ai, int family) +{ + while (ai) { + if (ai->ai_family == family) + return ai; + ai = ai->ai_next; + } + return NULL; +} + +struct evutil_addrinfo * +ai_find_by_protocol(struct evutil_addrinfo *ai, int protocol) +{ + while (ai) { + if (ai->ai_protocol == protocol) + return ai; + ai = ai->ai_next; + } + return NULL; +} + + +int +_test_ai_eq(const struct evutil_addrinfo *ai, const char *sockaddr_port, + int socktype, int protocol, int line) { struct sockaddr_storage ss; + int slen = sizeof(ss); + int gotport; + char buf[128]; + memset(&ss, 0, sizeof(ss)); + if (socktype > 0) + tt_int_op(ai->ai_socktype, ==, socktype); + if (protocol > 0) + tt_int_op(ai->ai_protocol, ==, protocol); + + if (evutil_parse_sockaddr_port( + sockaddr_port, (struct sockaddr*)&ss, &slen)<0) { + TT_FAIL(("Couldn't parse expected address %s on line %d", + sockaddr_port, line)); + return -1; + } + if (ai->ai_family != ss.ss_family) { + TT_FAIL(("Address family %d did not match %d on line %d", + ai->ai_family, ss.ss_family, line)); + return -1; + } + if (ai->ai_addr->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)ai->ai_addr; + evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)); + gotport = ntohs(sin->sin_port); + if (ai->ai_addrlen != sizeof(struct sockaddr_in)) { + TT_FAIL(("Addr size mismatch on line %d", line)); + return -1; + } + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)ai->ai_addr; + evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf)); + gotport = ntohs(sin6->sin6_port); + if (ai->ai_addrlen != sizeof(struct sockaddr_in6)) { + TT_FAIL(("Addr size mismatch on line %d", line)); + return -1; + } + } + if (evutil_sockaddr_cmp(ai->ai_addr, (struct sockaddr*)&ss, 1)) { + TT_FAIL(("Wanted %s, got %s:%d on line %d", sockaddr_port, + buf, gotport, line)); + return -1; + } else { + TT_BLATHER(("Wanted %s, got %s:%d on line %d", sockaddr_port, + buf, gotport, line)); + } + return 0; +end: + TT_FAIL(("Test failed on line %d", line)); + return -1; +} + +static void +test_evutil_getaddrinfo(void *arg) +{ + struct evutil_addrinfo *ai = NULL, *a; + struct evutil_addrinfo hints; + struct sockaddr_in6 *sin6; struct sockaddr_in *sin; - ev_socklen_t socklen = sizeof(ss); char buf[128]; const char *cp; int r; - memset(&ss, 0xff, sizeof(ss)); /* Make sure it starts out confused.*/ - r = evutil_resolve(AF_INET, "www.google.com", (struct sockaddr*)&ss, - &socklen, 80); - if (r<0) { - TT_BLATHER(("Couldn't resolve www.google.com")); - tt_skip(); + /* Try using it as a pton. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evutil_getaddrinfo("1.2.3.4", "8080", &hints, &ai); + tt_int_op(r, ==, 0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); /* no ambiguity */ + test_ai_eq(ai, "1.2.3.4:8080", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_UDP; + r = evutil_getaddrinfo("1001:b0b::f00f", "4321", &hints, &ai); + tt_int_op(r, ==, 0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); /* no ambiguity */ + test_ai_eq(ai, "[1001:b0b::f00f]:4321", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Try out the behavior of nodename=NULL */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = EVUTIL_AI_PASSIVE; /* as if for bind */ + r = evutil_getaddrinfo(NULL, "9999", &hints, &ai); + tt_int_op(r,==,0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "0.0.0.0:9999", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + hints.ai_flags = 0; /* as if for connect */ + r = evutil_getaddrinfo(NULL, "9998", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + test_ai_eq(ai, "127.0.0.1:9998", SOCK_STREAM, IPPROTO_TCP); + tt_ptr_op(ai->ai_next, ==, NULL); + evutil_freeaddrinfo(ai); + ai = NULL; + + hints.ai_flags = 0; /* as if for connect */ + hints.ai_family = PF_INET6; + r = evutil_getaddrinfo(NULL, "9997", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "[::1]:9997", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + hints.ai_flags = EVUTIL_AI_PASSIVE; /* as if for bind. */ + hints.ai_family = PF_INET6; + r = evutil_getaddrinfo(NULL, "9996", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "[::]:9996", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Now try an unspec one. We should get a v6 and a v4. */ + hints.ai_family = PF_UNSPEC; + r = evutil_getaddrinfo(NULL, "9996", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + a = ai_find_by_family(ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::]:9996", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_family(ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "0.0.0.0:9996", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Try out AI_NUMERICHOST: successful case. Also try + * multiprotocol. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + r = evutil_getaddrinfo("1.2.3.4", NULL, &hints, &ai); + tt_int_op(r, ==, 0); + a = ai_find_by_protocol(ai, IPPROTO_TCP); + tt_assert(a); + test_ai_eq(a, "1.2.3.4", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_protocol(ai, IPPROTO_UDP); + tt_assert(a); + test_ai_eq(a, "1.2.3.4", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Try the failing case of AI_NUMERICHOST */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + r = evutil_getaddrinfo("www.google.com", "80", &hints, &ai); + tt_int_op(r, ==, EVUTIL_EAI_NONAME); + tt_int_op(ai, ==, NULL); + + /* Try symbolic service names wit AI_NUMERICSERV */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_NUMERICSERV; + r = evutil_getaddrinfo("1.2.3.4", "http", &hints, &ai); + tt_int_op(r,==,EVUTIL_EAI_NONAME); + + /* Try symbolic service names */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evutil_getaddrinfo("1.2.3.4", "http", &hints, &ai); + if (r!=0) { + TT_GRIPE(("Symbolic service names seem broken.")); + } else { + tt_assert(ai); + test_ai_eq(ai, "1.2.3.4:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; } - tt_int_op(ss.ss_family, ==, AF_INET); - tt_int_op(socklen, ==, sizeof(struct sockaddr_in)); - sin = (struct sockaddr_in*)&ss; - tt_int_op(sin->sin_port, ==, htons(80)); - tt_int_op(sin->sin_addr.s_addr, !=, 0xffffffff); - cp = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)); - TT_BLATHER(("www.google.com resolved to %s",cp?cp:"")); + /* Now do some actual lookups. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + r = evutil_getaddrinfo("www.google.com", "80", &hints, &ai); + if (r != 0) { + TT_GRIPE(("Couldn't resolve www.google.com")); + } else { + tt_assert(ai); + tt_int_op(ai->ai_family, ==, PF_INET); + tt_int_op(ai->ai_protocol, ==, IPPROTO_TCP); + tt_int_op(ai->ai_socktype, ==, SOCK_STREAM); + tt_int_op(ai->ai_addrlen, ==, sizeof(struct sockaddr_in)); + sin = (struct sockaddr_in*)ai->ai_addr; + tt_int_op(sin->sin_family, ==, AF_INET); + tt_int_op(sin->sin_port, ==, htons(80)); + tt_int_op(sin->sin_addr.s_addr, !=, 0xffffffff); - memset(&ss, 0xff, sizeof(ss)); /* Make sure it starts out confused.*/ - socklen = sizeof(ss); - r = evutil_resolve(AF_INET6, "ipv6.google.com", (struct sockaddr*)&ss, - &socklen, 80); - if (r<0) { + cp = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)); + TT_BLATHER(("www.google.com resolved to %s", + cp?cp:"")); + evutil_freeaddrinfo(ai); + ai = NULL; + } + + hints.ai_family = PF_INET6; + r = evutil_getaddrinfo("ipv6.google.com", "80", &hints, &ai); + if (r != 0) { TT_BLATHER(("Couldn't do an ipv6 lookup for ipv6.google.com")); - goto end; - } - tt_int_op(ss.ss_family, ==, AF_INET6); - tt_int_op(socklen, ==, sizeof(struct sockaddr_in6)); - sin6 = (struct sockaddr_in6*)&ss; - tt_int_op(sin6->sin6_port, ==, htons(80)); + } else { + tt_assert(ai); + tt_int_op(ai->ai_family, ==, PF_INET6); + tt_int_op(ai->ai_addrlen, ==, sizeof(struct sockaddr_in6)); + sin6 = (struct sockaddr_in6*)ai->ai_addr; + tt_int_op(sin6->sin6_port, ==, htons(80)); - cp = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf)); - TT_BLATHER(("ipv6.google.com resolved to %s",cp?cp:"")); + cp = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, + sizeof(buf)); + TT_BLATHER(("ipv6.google.com resolved to %s", + cp?cp:"")); + } end: - ; + if (ai) + evutil_freeaddrinfo(ai); } struct testcase_t util_testcases[] = { @@ -546,7 +762,7 @@ struct testcase_t util_testcases[] = { { "strlcpy", test_evutil_strlcpy, 0, NULL, NULL }, { "log", test_evutil_log, TT_FORK, NULL, NULL }, { "upcast", test_evutil_upcast, 0, NULL, NULL }, - { "resolve", test_evutil_resolve, TT_FORK, NULL, NULL }, + { "getaddrinfo", test_evutil_getaddrinfo, TT_FORK, NULL, NULL }, END_OF_TESTCASES, }; diff --git a/util-internal.h b/util-internal.h index 6f524d3a..36ec3239 100644 --- a/util-internal.h +++ b/util-internal.h @@ -205,6 +205,35 @@ const char *evutil_getenv(const char *name); #define EV_SIZE_MAX ((size_t)-1) #endif +/* Internal addrinfo error code. This one is returned from only from + * evutil_getaddrinfo_common, when we are sure that we'll have to hit a DNS + * server. */ +#define EVUTIL_EAI_NEED_RESOLVE -90002 + +struct evdns_base; +struct evdns_getaddrinfo_request; +typedef struct evdns_getaddrinfo_request* (*evdns_getaddrinfo_fn)( + struct evdns_base *base, + const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, + void (*cb)(int, struct evutil_addrinfo *, void *), void *arg); + +void evutil_set_evdns_getaddrinfo_fn(evdns_getaddrinfo_fn fn); + +struct evutil_addrinfo *evutil_new_addrinfo(struct sockaddr *sa, + ev_socklen_t socklen, const struct evutil_addrinfo *hints); +struct evutil_addrinfo *evutil_addrinfo_append(struct evutil_addrinfo *first, + struct evutil_addrinfo *append); +void evutil_adjust_hints_for_addrconfig(struct evutil_addrinfo *hints); +int evutil_getaddrinfo_common(const char *nodename, const char *servname, + struct evutil_addrinfo *hints, struct evutil_addrinfo **res, int *portnum); + +int +evutil_getaddrinfo_async(struct evdns_base *dns_base, + const char *nodename, const char *servname, + const struct evutil_addrinfo *hints_in, + void (*cb)(int, struct evutil_addrinfo *, void *), void *arg); + #ifdef __cplusplus } #endif