]> granicus.if.org Git - libevent/commitdiff
Add two implementations of getaddrinfo: one blocking and one nonblocking.
authorNick Mathewson <nickm@torproject.org>
Mon, 16 Nov 2009 22:25:46 +0000 (22:25 +0000)
committerNick Mathewson <nickm@torproject.org>
Mon, 16 Nov 2009 22:25:46 +0000 (22:25 +0000)
The entry points are evutil_getaddrinfo and evdns_getaddrinfo respectively.
There are fairly extensive unit tests.

I believe this code conforms to RFC3493 pretty closely, but there are
probably more issues.  It should get tested on more platforms.

This code means we can dump the well-intentioned but weirdly-implemented
bufferevent_evdns and evutil_resolve code.

svn:r1537

20 files changed:
ChangeLog
Makefile.am
Makefile.nmake
WIN32-Code/event-config.h
bufferevent-internal.h
bufferevent_evdns.c [deleted file]
bufferevent_sock.c
configure.in
evdns.c
evutil.c
http.c
include/event2/dns.h
include/event2/dns_compat.h
include/event2/util.h
sample/dns-example.c
test/regress.h
test/regress_dns.c
test/regress_http.c
test/regress_util.c
util-internal.h

index 41cc49353853a605faa3b3df27befe2f2efb7e1a..b6b031ca30119ccf97c6c3d2a00e77a4a4771e48 100644 (file)
--- 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:
index e62376d8836c12ee4684f032b31d5a044b5f37fc..b1fa3ca227bc58a11f778a11522fb6815a7117e0 100644 (file)
@@ -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)
index 8d56dface9235cef9e40f6ba23a9ac8f546ae85e..73e5feee20674e6c6d8e8ddb63b0d6fd1ed04e68 100644 (file)
@@ -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
index 5f0e76a313030ab5f07b7fea39155e9bc6550f1e..4a7f6b7ed2463ad11b0f6afd96577e45fa7fe4a9 100644 (file)
 #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 */
 /* 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
 
index 4ee6d908f92d045a0f8e940cc12509a89df62999..732646bd6f0257837435c0e2942f0bb65e9758f6 100644 (file)
@@ -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 (file)
index ae75d55..0000000
+++ /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 <winsock2.h>
-#include <ws2tcpip.h>
-#endif
-
-#include "event-config.h"
-
-#include <sys/types.h>
-#ifdef _EVENT_HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
-#ifdef _EVENT_HAVE_ARPA_INET_H
-#include <arpa/inet.h>
-#endif
-#ifdef _EVENT_HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#endif
-#ifdef _EVENT_HAVE_NETINET_IN6_H
-#include <netinet/in6.h>
-#endif
-#include <stdlib.h>
-#include <string.h>
-
-#include <event2/event.h>
-#include <event2/bufferevent.h>
-#include <event2/bufferevent_struct.h>
-#include <event2/dns.h>
-#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;
-}
-
index f3551a3f62e688cc9170462d16c8a721e5f73e1e..5477d074f9da61c9ca6a6ff525ca15c556bab7a0 100644 (file)
@@ -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);
 }
 
 /*
index 6f826f43a1d4a29127a36fc158887757dcb047af..c8913485c0407be7edf6f926cd150dcb13090937 100644 (file)
@@ -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 <netdb.h>
+  ], [[
+    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 <netdb.h>
+    ], [
+      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 <netdb.h>
+     ], [
+       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 <sys/types.h>
+AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t, struct addrinfo], , ,
+[#define _GNU_SOURCE
+#include <sys/types.h>
 #ifdef HAVE_NETINET_IN_H
 #include <netinet/in.h>
 #endif
@@ -379,6 +442,9 @@ AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t], , ,
 #ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
 #endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
 #ifdef WIN32
 #define WIN32_WINNT 0x400
 #define _WIN32_WINNT 0x400
diff --git a/evdns.c b/evdns.c
index e6d6888a0daa13e60bdcdc3c0bdf7c66f2515370..135d90d3bed54bf86678467dd545c98b729b3de4 100644 (file)
--- 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);
+}
index 072b22b4368804466e053e919a41c66654c0b04c..b67ee73ca1d516383a85ab4936cb78aa3013ea91 100644 (file)
--- a/evutil.c
+++ b/evutil.c
@@ -26,6 +26,9 @@
 
 #include "event-config.h"
 
+#define _REENTRANT
+#define _GNU_SOURCE
+
 #ifdef WIN32
 #include <winsock2.h>
 #include <ws2tcpip.h>
@@ -59,9 +62,6 @@
 #ifdef _EVENT_HAVE_NETINET_IN6_H
 #include <netinet/in6.h>
 #endif
-#ifdef _EVENT_HAVE_NETDB_H
-#include <netdb.h>
-#endif
 
 #ifndef _EVENT_HAVE_GETTIMEOFDAY
 #include <sys/timeb.h>
@@ -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.
- */
-int
-evutil_resolve(int family, const char *hostname, struct sockaddr *sa,
-    ev_socklen_t *socklen, int 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)
 {
-#ifdef _EVENT_HAVE_GETADDRINFO_XXX
-       struct addrinfo hint, *hintp=NULL;
-       struct addrinfo *ai=NULL;
+       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;
-       memset(&hint, 0, sizeof(hint));
+       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 (family != AF_UNSPEC) {
-               hint.ai_family = family;
-               hintp = &hint;
+       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;
        }
 
-       r = getaddrinfo(hostname, NULL, hintp, &ai);
+       /* 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 -1;
-       if (r || ai->ai_addrlen > *socklen) {
-               /* log/report error? */
-               freeaddrinfo(ai);
-               return -1;
+               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);
+               }
        }
-       /* 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);
+#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;
        }
-       *socklen = ai->ai_addrlen;
-       freeaddrinfo(ai);
-       return 0;
-#else
-       /* XXXX use gethostbyname_r/gethostbyname2_r where available */
-       struct hostent *he;
-       struct sockaddr *sa_ptr;
+#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_getaddrinfo_common(const char *nodename, const char *servname,
+    struct evutil_addrinfo *hints, struct evutil_addrinfo **res, int *portnum)
+{
+       int port = 0;
+       const char *pname;
+
+       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;
+               }
+       }
+
+       /* 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;
+       }
+
+       /* 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;
+               }
+       }
+
+       /* 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 ae12c923b56858668ce27becafe8580ba524b8f8..7371677950e48d3fc105310adf8fc9d7c1efb3a3 100644 (file)
--- a/http.c
+++ b/http.c
@@ -48,6 +48,8 @@
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
+#else
+#include <ws2tcpip.h>
 #endif
 
 #include <sys/queue.h>
 #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);
 }
index f05f71d143aea98f007f6b8d3c32d0161801a885..233cbf6d3283e57b1e85d315652115eb325e0261 100644 (file)
@@ -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
index 1e3c410ce8a0395237914c3c3bba964f1c0c2781..d9d2a255c32021e1fef4ff24344bc96f0ace72ab 100644 (file)
@@ -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.
 
index b2b9a8ab070b3060df027e5bad8e547d0eba2f8a..d96c231b1f2a836b4b1a8e042791f63f9bd081c3 100644 (file)
@@ -56,6 +56,10 @@ extern "C" {
 #include <BaseTsd.h>
 #endif
 #include <stdarg.h>
+#ifdef _EVENT_HAVE_NETDB_H
+#define _GNU_SOURCE
+#include <netdb.h>
+#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
index 7757dd48cda29d1595c2299e095b4f2b3d672ac1..fda7def0da1c953c976740110704cdae59f805ce 100644 (file)
@@ -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]);
index 0593a69ba8875cfc51f042892e2936e0b4b70b8a..be69a58e2ebed1335b61200a96aaa31813c7a6db 100644 (file)
@@ -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
 }
index d6f6783b575454cbe488d36a4db55d89b16aa55e..b7059c0524e3623295b7dbe6638fc7a558b4ef79 100644 (file)
@@ -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
 };
 
index 6bff6af70e615df32640e75c7e2c4d7440806f1b..6162f6049f0d9de1a539985529f0bb2c13aab907 100644 (file)
@@ -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
index 49cefa029ba3890a4c99a2562a2da4f464fc5db2..e474a34a56ab624e603415e310ec313cd1a2e95f 100644 (file)
@@ -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:"<unwriteable>"));
-
-       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) {
-               TT_BLATHER(("Couldn't do an ipv6 lookup for ipv6.google.com"));
-               goto end;
+
+       /* 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);
+
+               cp = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf));
+               TT_BLATHER(("www.google.com resolved to %s",
+                       cp?cp:"<unwriteable>"));
+               evutil_freeaddrinfo(ai);
+               ai = NULL;
        }
-       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));
 
-       cp = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf));
-       TT_BLATHER(("ipv6.google.com resolved to %s",cp?cp:"<unwriteable>"));
+       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"));
+       } 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:"<unwriteable>"));
+       }
 
 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,
 };
 
index 6f524d3a8f819f67bc5d9411e80e1c65d028fcfe..36ec3239306c1ebdcd52aff7b24c2df9e734238a 100644 (file)
@@ -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