summaryrefslogtreecommitdiff
path: root/src/lib/ecore_con/ecore_con_ares.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/ecore_con/ecore_con_ares.c')
-rw-r--r--src/lib/ecore_con/ecore_con_ares.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/src/lib/ecore_con/ecore_con_ares.c b/src/lib/ecore_con/ecore_con_ares.c
new file mode 100644
index 0000000000..3c0ca22c85
--- /dev/null
+++ b/src/lib/ecore_con/ecore_con_ares.c
@@ -0,0 +1,628 @@
+#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 <sys/types.h>
+
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+
+#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;
+ Ecore_Timer *timer;
+ 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;
+#ifdef HAVE_IPV6
+ struct in6_addr v6;
+#endif
+ } addr;
+
+ Eina_Bool byaddr : 1;
+ Eina_Bool isv6 : 1;
+};
+
+static ares_channel info_channel;
+static int info_init = 0;
+static Eina_List *info_fds = NULL;
+
+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 Eina_Bool _ecore_con_info_cares_fd_cb(Ecore_Con_FD *ecf,
+ Ecore_Fd_Handler *fd_handler);
+static Eina_Bool _ecore_con_info_cares_timeout_cb(void *data);
+
+static void
+_ecore_con_info_cares_state_cb(void *data,
+ ares_socket_t fd,
+ int readable,
+ int writable);
+static int
+_ecore_con_info_fds_search(const Ecore_Con_FD *fd1,
+ const Ecore_Con_FD *fd2);
+
+int
+ecore_con_info_init(void)
+{
+ struct ares_options opts;
+
+ if (!info_init)
+ {
+ if (ares_library_init(ARES_LIB_INIT_ALL))
+ return 0;
+
+ opts.lookups = "fb"; /* hosts file then dns */
+ opts.sock_state_cb = _ecore_con_info_cares_state_cb;
+
+ if (ares_init_options(&info_channel, &opts,
+ ARES_OPT_LOOKUPS | ARES_OPT_SOCK_STATE_CB) != ARES_SUCCESS)
+ {
+ ares_library_cleanup();
+ return 0;
+ }
+ }
+
+ info_init++;
+ return info_init;
+}
+
+int
+ecore_con_info_shutdown(void)
+{
+ info_init--;
+ if (info_init == 0)
+ {
+ /* Cancel all ongoing request */
+ ares_cancel(info_channel);
+ ares_destroy(info_channel);
+
+ /* 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));
+#ifdef HAVE_IPV6
+ hints.ai_family = AF_INET6;
+#else
+ hints.ai_family = AF_INET;
+#endif
+ 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));
+#ifdef HAVE_IPV6
+ hints.ai_family = AF_INET6;
+#else
+ hints.ai_family = AF_INET;
+#endif
+ 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));
+#ifdef HAVE_IPV6
+ hints.ai_family = AF_INET6;
+#else
+ hints.ai_family = AF_INET;
+#endif
+ 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));
+#ifdef HAVE_IPV6
+ hints.ai_family = AF_INET6;
+#else
+ hints.ai_family = AF_INET;
+#endif
+ 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));
+#ifdef HAVE_IPV6
+ hints.ai_family = AF_INET6;
+#else
+ hints.ai_family = AF_INET;
+#endif
+ 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);
+}
+
+static Eina_Bool
+_ecore_con_info_ares_getnameinfo(Ecore_Con_CAres *arg,
+ int addrtype,
+ const char *name,
+ struct sockaddr *addr,
+ int addrlen)
+{
+ int length = 0;
+
+ if (name)
+ length = strlen(name) + 1;
+ else
+ length = 1;
+
+ arg->result = malloc(sizeof(Ecore_Con_Info) + length);
+ if (!arg->result)
+ return EINA_FALSE;
+
+ /* 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 = addrtype;
+ arg->result->info.ai_addrlen = addrlen;
+ arg->result->info.ai_addr = addr;
+ arg->result->info.ai_canonname = (char *)(arg->result + 1);
+
+ if (!name)
+ *arg->result->info.ai_canonname = '\0';
+ else
+ strcpy(arg->result->info.ai_canonname, 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);
+
+ return EINA_TRUE;
+}
+
+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;
+#ifdef HAVE_IPV6
+ int ai_family = AF_INET6;
+#else
+ int ai_family = AF_INET;
+#endif
+
+ 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->ecs ? svr->ecs->ip : svr->name, &cares->addr.v4) == 1)
+ {
+ cares->byaddr = EINA_TRUE;
+ cares->isv6 = EINA_FALSE;
+ ares_gethostbyaddr(info_channel, &cares->addr.v4,
+ sizeof(cares->addr.v4),
+ AF_INET,
+ (ares_host_callback)_ecore_con_info_ares_host_cb,
+ cares);
+ }
+#ifdef HAVE_IPV6
+ else if (inet_pton(AF_INET6, svr->ecs ? svr->ecs->ip : svr->name, &cares->addr.v6) == 1)
+ {
+ cares->byaddr = EINA_TRUE;
+ cares->isv6 = 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);
+ }
+#endif
+ else
+ {
+ cares->byaddr = EINA_FALSE;
+ ares_gethostbyname(info_channel, svr->ecs ? svr->ecs->ip : svr->name, ai_family,
+ (ares_host_callback)_ecore_con_info_ares_host_cb,
+ cares);
+ }
+
+ svr->infos = eina_list_append(svr->infos, cares);
+ return 1;
+}
+
+void
+ecore_con_info_data_clear(void *info)
+{
+ Ecore_Con_CAres *cares = info;
+ if (cares) cares->data = NULL;
+}
+
+static Eina_Bool
+_ecore_con_info_cares_timeout_cb(void *data EINA_UNUSED)
+{
+ ares_process_fd(info_channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+ return ECORE_CALLBACK_RENEW;
+}
+
+static Eina_Bool
+_ecore_con_info_cares_fd_cb(Ecore_Con_FD *ecf,
+ Ecore_Fd_Handler *fd_handler)
+{
+ ares_socket_t read_fd, write_fd;
+
+ read_fd = write_fd = ARES_SOCKET_BAD;
+
+ if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_READ))
+ read_fd = ecf->fd;
+ if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_WRITE))
+ write_fd = ecf->fd;
+
+ ares_process_fd(info_channel, read_fd, write_fd);
+
+ return ECORE_CALLBACK_RENEW;
+}
+
+static int
+_ecore_con_info_fds_search(const Ecore_Con_FD *fd1,
+ const Ecore_Con_FD *fd2)
+{
+ return fd1->fd - fd2->fd;
+}
+
+static void
+_ecore_con_info_cares_state_cb(void *data EINA_UNUSED,
+ ares_socket_t fd,
+ int readable,
+ int writable)
+{
+ int flags = 0;
+ Ecore_Con_FD *search = NULL, *ecf = NULL;
+
+ search = eina_list_search_unsorted(info_fds,
+ (Eina_Compare_Cb)_ecore_con_info_fds_search, &ecf);
+
+ if (!(readable | writable))
+ {
+ ares_process_fd(info_channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+ if (search)
+ {
+ info_fds = eina_list_remove(info_fds, search);
+ ecore_timer_del(search->timer);
+ ecore_main_fd_handler_del(search->handler);
+ free(search);
+ }
+ return;
+ }
+
+ if (!search)
+ {
+ search = malloc(sizeof(Ecore_Con_FD));
+ EINA_SAFETY_ON_NULL_RETURN(search);
+
+ search->fd = fd;
+ search->handler = ecore_main_fd_handler_add(fd, ECORE_FD_WRITE | ECORE_FD_READ,
+ (Ecore_Fd_Cb)_ecore_con_info_cares_fd_cb, search, NULL, NULL);
+ /* c-ares default timeout is 5 seconds */
+ search->timer = ecore_timer_add(5, _ecore_con_info_cares_timeout_cb, NULL);
+ info_fds = eina_list_append(info_fds, search);
+ }
+
+ if (readable) flags |= ECORE_FD_READ;
+ if (writable) flags |= ECORE_FD_WRITE;
+ ecore_main_fd_handler_active_set(search->handler, flags);
+}
+
+static void
+_ecore_con_info_ares_host_cb(Ecore_Con_CAres *arg,
+ int status,
+ int timeouts EINA_UNUSED,
+ struct hostent *hostent)
+{
+ struct sockaddr *addr;
+ int addrlen;
+
+ /* Found something ? */
+ switch (status)
+ {
+ case ARES_SUCCESS:
+ if (!hostent->h_addr_list[0])
+ {
+ ERR("No IP found");
+ goto on_error;
+ }
+
+ switch (hostent->h_addrtype)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *addri;
+
+ addrlen = sizeof(struct sockaddr_in);
+ addri = malloc(addrlen);
+
+ if (!addri)
+ goto on_mem_error;
+
+ addri->sin_family = AF_INET;
+ addri->sin_port = htons(arg->svr->ecs ? arg->svr->ecs->port : arg->svr->port);
+
+ memcpy(&addri->sin_addr.s_addr,
+ hostent->h_addr_list[0], sizeof(struct in_addr));
+
+ addr = (struct sockaddr *)addri;
+ break;
+ }
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *addri6;
+
+ addrlen = sizeof(struct sockaddr_in6);
+ addri6 = malloc(addrlen);
+
+ if (!addri6)
+ goto on_mem_error;
+
+ addri6->sin6_family = AF_INET6;
+ addri6->sin6_port = htons(arg->svr->ecs ? arg->svr->ecs->port : arg->svr->port);
+ addri6->sin6_flowinfo = 0;
+ addri6->sin6_scope_id = 0;
+
+ memcpy(&addri6->sin6_addr.s6_addr,
+ hostent->h_addr_list[0], sizeof(struct in6_addr));
+
+ addr = (struct sockaddr *)addri6;
+ break;
+ }
+#endif
+ default:
+ ERR("Unknown addrtype %i", hostent->h_addrtype);
+ goto on_error;
+ }
+
+ if (!_ecore_con_info_ares_getnameinfo(arg, hostent->h_addrtype,
+ hostent->h_name,
+ addr, addrlen))
+ goto on_error;
+
+ break;
+
+ case ARES_ENOTFOUND: /* address notfound */
+ if (arg->byaddr)
+ {
+#ifdef HAVE_IPV6
+ /* This happen when host doesn't have a reverse. */
+ if (arg->isv6)
+ {
+ struct sockaddr_in6 *addri6;
+
+ addrlen = sizeof(struct sockaddr_in6);
+ addri6 = malloc(addrlen);
+
+ if (!addri6)
+ goto on_mem_error;
+
+ addri6->sin6_family = AF_INET6;
+ addri6->sin6_port = htons(arg->svr->ecs ? arg->svr->ecs->port : arg->svr->port);
+ addri6->sin6_flowinfo = 0;
+ addri6->sin6_scope_id = 0;
+
+ memcpy(&addri6->sin6_addr.s6_addr,
+ &arg->addr.v6, sizeof(struct in6_addr));
+
+ addr = (struct sockaddr *)addri6;
+ }
+ else
+#endif
+ {
+ struct sockaddr_in *addri;
+
+ addrlen = sizeof(struct sockaddr_in);
+ addri = malloc(addrlen);
+
+ if (!addri)
+ goto on_mem_error;
+
+ addri->sin_family = AF_INET;
+ addri->sin_port = htons(arg->svr->ecs ? arg->svr->ecs->port : arg->svr->port);
+
+ memcpy(&addri->sin_addr.s_addr,
+ &arg->addr.v4, sizeof(struct in_addr));
+
+ addr = (struct sockaddr *)addri;
+ }
+
+ if (!_ecore_con_info_ares_getnameinfo(arg,
+#ifdef HAVE_IPV6
+ arg->isv6 ? AF_INET6 :
+#endif
+ AF_INET,
+ NULL, addr,
+ addrlen))
+ goto on_error;
+
+ break;
+ }
+
+ case ARES_ENOTIMP: /* unknown family */
+ case ARES_EBADNAME: /* not a valid internet address */
+ case ARES_ENOMEM: /* not enough memory */
+ case ARES_EDESTRUCTION: /* request canceled, shuting down */
+ case ARES_ENODATA: /* no data returned */
+ case ARES_ECONNREFUSED: /* connection refused */
+ case ARES_ETIMEOUT: /* connection timed out */
+ ecore_con_event_server_error(arg->svr, ares_strerror(status));
+ goto on_error;
+
+ default:
+ ERR("Unknown status returned by c-ares: %i assuming error", status);
+ ecore_con_event_server_error(arg->svr, ares_strerror(status));
+ goto on_error;
+ }
+
+ return;
+
+on_mem_error:
+ ERR("Not enough memory");
+
+on_error:
+ if (arg->data)
+ {
+ ecore_con_server_infos_del(arg->data, arg);
+ arg->done_cb(arg->data, NULL);
+ }
+ free(arg);
+}
+
+static void
+_ecore_con_info_ares_nameinfo(Ecore_Con_CAres *arg,
+ int status,
+ int timeouts EINA_UNUSED,
+ 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';
+
+ if (arg->data) arg->done_cb(arg->data, arg->result);
+ break;
+
+ case ARES_ENOTIMP:
+ case ARES_ENOTFOUND:
+ case ARES_ENOMEM:
+ case ARES_EDESTRUCTION:
+ case ARES_EBADFLAGS:
+ ecore_con_event_server_error(arg->svr, ares_strerror(status));
+ if (arg->data) arg->done_cb(arg->data, NULL);
+ break;
+ }
+
+ free(arg->result->info.ai_addr);
+ free(arg->result);
+ if (arg->data) ecore_con_server_infos_del(arg->data, arg);
+ free(arg);
+}
+