* ecore_con: Add an alternative to getaddrinfo/fork by using c-ares.
authorcedric <cedric@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Thu, 3 Dec 2009 10:26:40 +0000 (10:26 +0000)
committercedric <cedric@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Thu, 3 Dec 2009 10:26:40 +0000 (10:26 +0000)
For more information http://c-ares.haxx.se/

git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/ecore@44170 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33

configure.ac
m4/ecore_check_options.m4
src/lib/ecore_con/Makefile.am
src/lib/ecore_con/ecore_con_ares.c [new file with mode: 0644]

index 872c4b6..e8d8dcf 100644 (file)
@@ -82,6 +82,7 @@ want_curl="no"
 want_abstract_sockets="no"
 want_gnutls="no"
 want_openssl="no"
+want_cares="no"
 want_cipher="no"
 want_signature="no"
 want_poll="yes"
@@ -797,6 +798,7 @@ ECORE_CHECK_MODULE([Con], [${want_ecore_con}])
 have_curl="no"
 have_gnutls="no"
 have_openssl="no"
+have_cares="no"
 if test "x${have_ecore_con}" = "xyes" ; then
 
    ECORE_CHECK_CURL([${want_curl}],
@@ -825,6 +827,15 @@ if test "x${have_ecore_con}" = "xyes" ; then
          # depends on ecore_con anyway.
       fi
    fi
+
+   ECORE_CHECK_CARES([${want_cares}],
+      [
+       have_cares="yes"
+       requirements_ecore_con="libcares ${requirements_ecore_con}"
+      ], [
+       have_cares="no"
+      ])
+
 fi
 
 # ecore_ipc
@@ -1193,6 +1204,7 @@ fi
   echo "    GnuTLS.....................: $have_gnutls"
   echo "    CURL.......................: $have_curl"
   echo "    Abstract Sockets...........: $want_abstract_sockets"
+  echo "    c-ares.....................: $have_cares"
 fi
 echo "  Ecore_Ipc....................: $have_ecore_ipc"
 if test "x$have_ecore_ipc" = "xyes" ; then
index d39d6f7..e11ca43 100644 (file)
@@ -272,3 +272,45 @@ else
    m4_default([$3], [:])
 fi
 ])
+
+dnl use: ECORE_CHECK_CARES(default-enabled[, ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+AC_DEFUN([ECORE_CHECK_CARES],
+[
+_cares_requirement=""
+_ecore_want_cares=$1
+_ecore_have_cares="no"
+CARES_LIBS=""
+CARES_CFLAGS=""
+
+AC_ARG_ENABLE(cares,
+  [AC_HELP_STRING([--disable-cares], [disable cares support])],
+  [
+    if test "x${enableval}" = "xyes" ; then
+       _ecore_want_cares="yes"
+    else
+       _ecore_want_cares="no"
+    fi
+  ])
+
+if test "x${_ecore_want_cares}" = "xyes" -o "x${_ecore_want_cares}" = "xauto" ; then
+   PKG_CHECK_MODULES([CARES], [libcares >= 1.6.1],
+     [
+      AC_DEFINE(HAVE_CARES, 1, [Build Ecore_Con_Info with c-ares support])
+      _ecore_have_cares="yes"
+      _cares_requirement="libcares"
+     ], [
+      _ecore_have_cares="no"
+     ])
+fi
+
+AM_CONDITIONAL(HAVE_CARES, test "x$_ecore_have_cares" = "xyes")
+
+AC_SUBST(CARES_LIBS)
+AC_SUBST(CARES_CFLAGS)
+
+if test "x$_ecore_have_cares" = "xyes" ; then
+   m4_default([$2], [:])
+else
+   m4_default([$3], [:])
+fi
+])
index 3477ac6..69e6684 100644 (file)
@@ -5,7 +5,7 @@ AM_CPPFLAGS = \
 -I$(top_builddir)/src/lib/ecore_con \
 -I$(top_srcdir)/src/lib/ecore \
 -I$(top_srcdir)/src/lib/ecore_con \
-@SSL_CFLAGS@ @CURL_CFLAGS@ @EINA_CFLAGS@ @TLS_CFLAGS@
+@SSL_CFLAGS@ @CURL_CFLAGS@ @EINA_CFLAGS@ @TLS_CFLAGS@ @CARES_CFLAGS@
 
 if BUILD_ECORE_CON
 
@@ -16,13 +16,18 @@ Ecore_Con.h
 libecore_con_la_SOURCES = \
 ecore_con.c \
 ecore_con_dns.c \
-ecore_con_info.c \
 ecore_con_ssl.c \
 ecore_con_url.c
 
+if HAVE_CARES
+libecore_con_la_SOURCES += ecore_con_ares.c
+else
+libecore_con_la_SOURCES += ecore_con_info.c
+endif
+
 libecore_con_la_LIBADD = \
 $(top_builddir)/src/lib/ecore/libecore.la \
-@SSL_LIBS@ @CURL_LIBS@ @EINA_LIBS@ @TLS_LIBS@
+@SSL_LIBS@ @CURL_LIBS@ @EINA_LIBS@ @TLS_LIBS@ @CARES_LIBS@
 
 libecore_con_la_LDFLAGS = -no-undefined -version-info @version_info@ @ecore_con_release_info@
 
diff --git a/src/lib/ecore_con/ecore_con_ares.c b/src/lib/ecore_con/ecore_con_ares.c
new file mode 100644 (file)
index 0000000..0a660ef
--- /dev/null
@@ -0,0 +1,484 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * This version of ecore_con_info use c-ares to provide asynchronous dns lookup.
+ *
+ * Note: It doesn't fork nor does it use libc getaddrinfo.
+ * http://c-ares.haxx.se/docs.html
+ */
+
+#include <string.h>
+#include <arpa/inet.h>
+#include <ares.h>
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+typedef struct _Ecore_Con_FD Ecore_Con_FD;
+typedef struct _Ecore_Con_CAres Ecore_Con_CAres;
+
+struct _Ecore_Con_FD
+{
+   Ecore_Fd_Handler *handler;
+   int active;
+   int fd;
+};
+
+struct _Ecore_Con_CAres
+{
+   Ecore_Con_Server *svr;
+   Ecore_Con_Info_Cb done_cb;
+   void *data;
+   struct addrinfo hints;
+   Ecore_Con_Info *result;
+
+   union {
+      struct in_addr v4;
+      struct in6_addr v6;
+   } addr;
+
+   Eina_Bool byaddr;
+};
+
+static ares_channel info_channel;
+static int info_init = 0;
+static Eina_List *info_fds = NULL;
+static int active = 0;
+static Ecore_Timer *tm = NULL;
+static fd_set info_readers, info_writers;
+
+static void _ecore_con_info_ares_nameinfo(Ecore_Con_CAres *arg, int status, int timeouts, char *node, char *service);
+static void _ecore_con_info_ares_host_cb(Ecore_Con_CAres *arg, int status, int timeouts, struct hostent *hostent);
+static int _ecore_con_info_cares_fd_cb(void *data, Ecore_Fd_Handler *fd_handler);
+static int _ecore_con_info_cares_timeout_cb(void *data);
+static void _ecore_con_info_cares_clean(void);
+
+EAPI int
+ecore_con_info_init(void)
+{
+   if (info_init == 0)
+     {
+       if (ares_library_init(ARES_LIB_INIT_ALL) != 0)
+         return 0;
+       if (ares_init(&info_channel) != ARES_SUCCESS)
+         {
+            ares_library_cleanup();
+            return 0;
+         }
+     }
+
+   info_init++;
+   return info_init;
+}
+
+EAPI int
+ecore_con_info_shutdown(void)
+{
+   info_init--;
+   if (info_init == 0)
+     {
+       /* Cancel all ongoing request */
+       ares_cancel(info_channel);
+       ares_destroy(info_channel);
+
+       /* Destroy FD handler here. */
+       /* Shutdown ares */
+       ares_library_cleanup();
+     }
+   return info_init;
+}
+
+int
+ecore_con_info_tcp_connect(Ecore_Con_Server *svr,
+                          Ecore_Con_Info_Cb done_cb,
+                          void *data)
+{
+   struct addrinfo hints;
+
+   memset(&hints, 0, sizeof(struct addrinfo));
+   hints.ai_family = AF_UNSPEC;
+   hints.ai_socktype = SOCK_STREAM;
+   hints.ai_flags = AI_CANONNAME;
+   hints.ai_protocol = IPPROTO_TCP;
+   hints.ai_canonname = NULL;
+   hints.ai_next = NULL;
+   hints.ai_addr = NULL;
+
+   return ecore_con_info_get(svr, done_cb, data, &hints);
+}
+
+int
+ecore_con_info_tcp_listen(Ecore_Con_Server *svr,
+                         Ecore_Con_Info_Cb done_cb,
+                         void *data)
+{
+   struct addrinfo hints;
+
+   memset(&hints, 0, sizeof(struct addrinfo));
+   hints.ai_family = AF_UNSPEC;
+   hints.ai_socktype = SOCK_STREAM;
+   hints.ai_flags = AI_PASSIVE;
+   hints.ai_protocol = IPPROTO_TCP;
+   hints.ai_canonname = NULL;
+   hints.ai_next = NULL;
+   hints.ai_addr = NULL;
+
+   return ecore_con_info_get(svr, done_cb, data, &hints);
+}
+
+int
+ecore_con_info_udp_connect(Ecore_Con_Server *svr,
+                          Ecore_Con_Info_Cb done_cb,
+                          void *data)
+{
+   struct addrinfo hints;
+
+   memset(&hints, 0, sizeof(struct addrinfo));
+   hints.ai_family = AF_UNSPEC;
+   hints.ai_socktype = SOCK_DGRAM;
+   hints.ai_flags = AI_CANONNAME;
+   hints.ai_protocol = IPPROTO_UDP;
+   hints.ai_canonname = NULL;
+   hints.ai_next = NULL;
+   hints.ai_addr = NULL;
+
+   return ecore_con_info_get(svr, done_cb, data, &hints);
+}
+
+int
+ecore_con_info_udp_listen(Ecore_Con_Server *svr,
+                         Ecore_Con_Info_Cb done_cb,
+                         void *data)
+{
+   struct addrinfo hints;
+
+   memset(&hints, 0, sizeof(struct addrinfo));
+   hints.ai_family = AF_UNSPEC;
+   hints.ai_socktype = SOCK_DGRAM;
+   hints.ai_flags = AI_PASSIVE;
+   hints.ai_protocol = IPPROTO_UDP;
+   hints.ai_canonname = NULL;
+   hints.ai_next = NULL;
+   hints.ai_addr = NULL;
+
+   return ecore_con_info_get(svr, done_cb, data, &hints);
+}
+
+int
+ecore_con_info_mcast_listen(Ecore_Con_Server *svr,
+                          Ecore_Con_Info_Cb done_cb,
+                          void *data)
+{
+   struct addrinfo hints;
+
+   memset(&hints, 0, sizeof(struct addrinfo));
+   hints.ai_family = AF_UNSPEC;
+   hints.ai_socktype = SOCK_DGRAM;
+   hints.ai_flags = 0;
+   hints.ai_protocol = IPPROTO_UDP;
+   hints.ai_canonname = NULL;
+   hints.ai_next = NULL;
+   hints.ai_addr = NULL;
+
+   return ecore_con_info_get(svr, done_cb, data, &hints);
+}
+
+EAPI int
+ecore_con_info_get(Ecore_Con_Server *svr,
+                  Ecore_Con_Info_Cb done_cb,
+                  void *data,
+                  struct addrinfo *hints)
+{
+   Ecore_Con_CAres *cares;
+   int ai_family = AF_UNSPEC;
+
+   cares = calloc(1, sizeof (Ecore_Con_CAres));
+   if (!cares) return 0;
+
+   cares->svr = svr;
+   cares->done_cb = done_cb;
+   cares->data = data;
+
+   if (hints)
+     {
+       ai_family = hints->ai_family;
+       memcpy(&cares->hints, hints, sizeof (struct addrinfo));
+     }
+
+   if (inet_pton(AF_INET, svr->name, &cares->addr.v4) == 1)
+     {
+       cares->byaddr = EINA_TRUE;
+       ares_gethostbyaddr(info_channel, &cares->addr.v4, sizeof (cares->addr.v4), AF_INET, (ares_host_callback) _ecore_con_info_ares_host_cb, cares);
+     }
+   else if (inet_pton(AF_INET6, svr->name, &cares->addr.v6) == 1)
+     {
+       cares->byaddr = EINA_TRUE;
+       ares_gethostbyaddr(info_channel, &cares->addr.v6, sizeof (cares->addr.v6), AF_INET6, (ares_host_callback) _ecore_con_info_ares_host_cb, cares);
+     }
+   else
+     {
+       cares->byaddr = EINA_FALSE;
+       ares_gethostbyname(info_channel, svr->name, ai_family, (ares_host_callback) _ecore_con_info_ares_host_cb, cares);
+     }
+
+   _ecore_con_info_cares_clean();
+
+   return 1;
+}
+
+static int
+_ecore_con_info_fds_search(const Ecore_Con_FD *fd1, const Ecore_Con_FD *fd2)
+{
+   return fd1->fd - fd2->fd;
+}
+
+static Eina_Bool
+_ecore_con_info_fds_lookup(int fd)
+{
+   Ecore_Con_FD fdl;
+   Ecore_Con_FD *search;
+
+   fdl.fd = fd;
+
+   search = eina_list_search_unsorted(info_fds, (Eina_Compare_Cb) _ecore_con_info_fds_search, &fdl);
+
+   if (search)
+     {
+       search->active = active;
+       return EINA_TRUE;
+     }
+   return EINA_FALSE;
+}
+
+static void
+_ecore_con_info_cares_clean(void)
+{
+   fd_set readers, writers;
+   Eina_List *l, *l_next;
+   Ecore_Con_FD *ecf;
+   int nfds;
+   int i;
+
+   FD_ZERO(&readers);
+   FD_ZERO(&writers);
+   nfds = ares_fds(info_channel, &readers, &writers);
+
+   active++;
+   for (i = 0; i < nfds; ++i)
+     {
+       int flags = 0;
+
+       if (FD_ISSET(i, &readers)) flags |= ECORE_FD_READ;
+       if (FD_ISSET(i, &writers)) flags |= ECORE_FD_WRITE;
+
+       if (flags)
+         {
+            if (!_ecore_con_info_fds_lookup(i))
+              {
+                 ecf = malloc(sizeof (Ecore_Con_FD));
+                 if (ecf)
+                   {
+                      ecf->fd = i;
+                      ecf->active = active;
+                      ecf->handler = ecore_main_fd_handler_add(i, ECORE_FD_WRITE | ECORE_FD_READ,
+                                                               _ecore_con_info_cares_fd_cb,
+                                                               NULL, NULL, NULL);
+                      info_fds = eina_list_append(info_fds, ecf);
+                   }
+              }
+         }
+     }
+
+   info_readers = readers;
+   info_writers = writers;
+
+   EINA_LIST_FOREACH_SAFE(info_fds, l, l_next, ecf)
+     {
+       if (ecf->active != active)
+         {
+            ecore_main_fd_handler_del(ecf->handler);
+            free(ecf);
+            info_fds = eina_list_remove_list(info_fds, l);
+         }
+     }
+
+   if (!info_fds)
+     {
+       if (tm) ecore_timer_del(tm);
+       tm = NULL;
+     }
+   else
+     {
+       struct timeval tv;
+
+       ares_timeout(info_channel, NULL, &tv);
+
+       if (tm)
+         ecore_timer_delay(tm, tv.tv_sec);
+       else
+         tm = ecore_timer_add((double) tv.tv_sec, _ecore_con_info_cares_timeout_cb, NULL);
+     }
+}
+
+static int
+_ecore_con_info_cares_timeout_cb(void *data)
+{
+   ares_process(info_channel, &info_readers, &info_writers);
+   _ecore_con_info_cares_clean();
+
+   return 1;
+}
+
+static int
+_ecore_con_info_cares_fd_cb(void *data, Ecore_Fd_Handler *fd_handler)
+{
+   ares_process(info_channel, &info_readers, &info_writers);
+   _ecore_con_info_cares_clean();
+
+   return 1;
+}
+
+static void
+_ecore_con_info_ares_host_cb(Ecore_Con_CAres *arg, int status, int timeouts, struct hostent *hostent)
+{
+   struct sockaddr *addr;
+   int addrlen;
+   int length = 0;
+
+   /* Found something ? */
+   switch (status)
+     {
+      case ARES_SUCCESS:
+        if (hostent->h_addr_list[0] == NULL)
+          {
+             fprintf(stderr, "No IP found\n");
+             goto on_error;
+          }
+
+        switch (hostent->h_addrtype)
+          {
+           case AF_INET:
+             {
+                struct sockaddr_in *addri;
+
+                addrlen = sizeof (struct sockaddr_in);
+                addri = malloc(addrlen);
+
+                if (!addri)
+                  {
+                     fprintf(stderr, "Not enough memory\n");
+                     goto on_error;
+                  }
+
+                addri->sin_family = AF_INET;
+                addri->sin_port = htons(arg->svr->port);
+
+                memcpy(&addri->sin_addr.s_addr, arg->byaddr ? &arg->addr.v4 : (struct in_addr*)hostent->h_addr_list[0], sizeof (struct in_addr));
+
+                addr = (struct sockaddr*) addri;
+                break;
+             }
+           case AF_INET6:
+             {
+                struct sockaddr_in6 *addri6;
+
+                addrlen = sizeof (struct sockaddr_in6);
+                addri6 = malloc(addrlen);
+
+                if (!addri6)
+                  {
+                     fprintf(stderr, "Not enough memory\n");
+                     goto on_error;
+                  }
+
+                addri6->sin6_family = AF_INET6;
+                addri6->sin6_port = htons(arg->svr->port);
+                addri6->sin6_flowinfo = 0;
+                addri6->sin6_scope_id = 0;
+
+                memcpy(&addri6->sin6_addr.s6_addr, arg->byaddr ? &arg->addr.v6 : (struct in6_addr*)hostent->h_addr_list[0], sizeof (struct in6_addr));
+
+                addr = (struct sockaddr*) addri6;
+                break;
+             }
+           default:
+              fprintf(stderr, "Unknown addrtype %i\n", hostent->h_addrtype);
+              goto on_error;
+          }
+
+        if (hostent->h_name)
+          length = strlen(hostent->h_name) + 1;
+
+        arg->result = malloc(sizeof (Ecore_Con_Info) + length);
+        if (!arg->result)
+          {
+             fprintf(stderr, "Not enough memory\n");
+             free(addr);
+             goto on_error;
+          }
+
+        /* FIXME: What to do when hint is not set ? */
+        arg->result->info.ai_flags = arg->hints.ai_flags;
+        arg->result->info.ai_socktype = arg->hints.ai_socktype;
+        arg->result->info.ai_protocol = arg->hints.ai_protocol;
+
+        arg->result->info.ai_family = hostent->h_addrtype;
+        arg->result->info.ai_addrlen = addrlen;
+        arg->result->info.ai_addr = addr;
+        arg->result->info.ai_canonname = (char*) (arg->result + 1);
+
+        strcpy(arg->result->info.ai_canonname, hostent->h_name);
+
+        arg->result->info.ai_next = NULL;
+
+        ares_getnameinfo(info_channel, addr, addrlen,
+                         ARES_NI_NUMERICSERV | ARES_NI_NUMERICHOST | ARES_NI_LOOKUPSERVICE | ARES_NI_LOOKUPHOST,
+                         (ares_nameinfo_callback) _ecore_con_info_ares_nameinfo, arg);
+        break;
+      case ARES_ENOTIMP: /* unknown family */
+      case ARES_EBADNAME: /* not a valid internet address */
+      case ARES_ENOTFOUND: /* address notfound */
+      case ARES_ENOMEM: /* not enough memory */
+      case ARES_EDESTRUCTION: /* request canceled, shuting down */
+        goto on_error;
+      default:
+        fprintf(stderr, "Unknown status returned by c-ares: %i assuming error\n", status);
+        goto on_error;
+     }
+
+   return ;
+
+ on_error:
+   arg->done_cb(arg->data, NULL);
+   free(arg);
+}
+
+static void
+_ecore_con_info_ares_nameinfo(Ecore_Con_CAres *arg, int status, int timeouts, char *node, char *service)
+{
+   switch (status)
+     {
+      case ARES_SUCCESS:
+        if (node) strcpy(arg->result->ip, node);
+        else *arg->result->ip = '\0';
+        if (service) strcpy(arg->result->service, service);
+        else *arg->result->service = '\0';
+
+        arg->done_cb(arg->data, arg->result);
+        break;
+      case ARES_ENOTIMP:
+      case ARES_ENOTFOUND:
+      case ARES_ENOMEM:
+      case ARES_EDESTRUCTION:
+      case ARES_EBADFLAGS:
+        arg->done_cb(arg->data, NULL);
+        break;
+     }
+
+   free(arg->result->info.ai_addr);
+   free(arg->result);
+   free(arg);
+}