barbieri pushed a commit to branch master.

http://git.enlightenment.org/core/efl.git/commit/?id=094c9091b4b39ec7f9371082a8f29b065f7252b8

commit 094c9091b4b39ec7f9371082a8f29b065f7252b8
Author: Gustavo Sverzut Barbieri <[email protected]>
Date:   Fri Oct 21 00:15:09 2016 -0200

    efl_net_server_tcp: use async getaddrinfo() to resolve server name.
    
    this allows nicer usage such as 'localhost:http' as the address, which
    will resolve to [::1]:80 (if IPv6 is enabled) or 127.0.0.1:80 if only
    IPv4 exists.
---
 src/lib/ecore_con/ecore_con.c           | 122 +++++++++++++++++++++
 src/lib/ecore_con/ecore_con_private.h   |  20 ++++
 src/lib/ecore_con/efl_net_server.eo     |   2 +
 src/lib/ecore_con/efl_net_server_tcp.c  | 185 +++++++++++++++++---------------
 src/lib/ecore_con/efl_net_server_tcp.eo |   1 +
 5 files changed, 245 insertions(+), 85 deletions(-)

diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c
index 270fd29..3c90af1 100644
--- a/src/lib/ecore_con/ecore_con.c
+++ b/src/lib/ecore_con/ecore_con.c
@@ -184,6 +184,8 @@ EWAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY 
= 0;
 EWAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST = 0;
 EWAPI Eina_Error EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = 0;
 
+EWAPI Eina_Error EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST = 0;
+
 static Eina_List *servers = NULL;
 static int _ecore_con_init_count = 0;
 static int _ecore_con_event_count = 0;
@@ -239,6 +241,8 @@ ecore_con_init(void)
    EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST = 
eina_error_msg_static_register("Couldn't resolve host name");
    EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = 
eina_error_msg_static_register("Proxy authentication failed");
 
+   EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST = 
eina_error_msg_static_register("Couldn't resolve host name");
+
    eina_magic_string_set(ECORE_MAGIC_CON_SERVER, "Ecore_Con_Server");
    eina_magic_string_set(ECORE_MAGIC_CON_CLIENT, "Ecore_Con_Client");
    eina_magic_string_set(ECORE_MAGIC_CON_URL, "Ecore_Con_Url");
@@ -3153,6 +3157,124 @@ efl_net_socket4(int domain, int type, int protocol, 
Eina_Bool close_on_exec)
    return fd;
 }
 
+typedef struct _Efl_Net_Ip_Resolve_Async_Data
+{
+   Efl_Net_Ip_Resolve_Async_Cb cb;
+   const void *data;
+   char *host;
+   char *port;
+   struct addrinfo *result;
+   struct addrinfo *hints;
+   int gai_error;
+} Efl_Net_Ip_Resolve_Async_Data;
+
+static void
+_efl_net_ip_resolve_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+   Efl_Net_Ip_Resolve_Async_Data *d = data;
+
+   /* allows ecore_thread_cancel() to cancel at some points, see
+    * man:pthreads(7).
+    *
+    * no need to set cleanup functions since the main thread will
+    * handle that with _efl_net_ip_resolve_async_cancel().
+    */
+   eina_thread_cancellable_set(EINA_TRUE, NULL);
+
+   while (EINA_TRUE)
+     {
+        DBG("resolving host='%s' port='%s'", d->host, d->port);
+        d->gai_error = getaddrinfo(d->host, d->port, d->hints, &d->result);
+        if (d->gai_error == 0) break;
+        if (d->gai_error == EAI_AGAIN) continue;
+        if ((d->gai_error == EAI_SYSTEM) && (errno == EINTR)) continue;
+
+        DBG("getaddrinfo(\"%s\", \"%s\") failed: %s", d->host, d->port, 
gai_strerror(d->gai_error));
+        break;
+     }
+
+   eina_thread_cancellable_set(EINA_FALSE, NULL);
+
+   if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+     {
+        char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+        const struct addrinfo *addrinfo;
+        for (addrinfo = d->result; addrinfo != NULL; addrinfo = 
addrinfo->ai_next)
+          {
+             if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
+               DBG("resolved host='%s' port='%s': %s", d->host, d->port, buf);
+          }
+     }
+}
+
+static void
+_efl_net_ip_resolve_async_data_free(Efl_Net_Ip_Resolve_Async_Data *d)
+{
+   free(d->hints);
+   free(d->host);
+   free(d->port);
+   free(d);
+}
+
+static void
+_efl_net_ip_resolve_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+   Efl_Net_Ip_Resolve_Async_Data *d = data;
+   d->cb((void *)d->data, d->host, d->port, d->hints, d->result, d->gai_error);
+   _efl_net_ip_resolve_async_data_free(d);
+}
+
+static void
+_efl_net_ip_resolve_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+   Efl_Net_Ip_Resolve_Async_Data *d = data;
+   if (d->result) freeaddrinfo(d->result);
+   _efl_net_ip_resolve_async_data_free(d);
+}
+
+Ecore_Thread *
+efl_net_ip_resolve_async_new(const char *host, const char *port, const struct 
addrinfo *hints, Efl_Net_Ip_Resolve_Async_Cb cb, const void *data)
+{
+   Efl_Net_Ip_Resolve_Async_Data *d;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(host, NULL);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(port, NULL);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
+
+   d = malloc(sizeof(Efl_Net_Ip_Resolve_Async_Data));
+   EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
+
+   d->cb = cb;
+   d->data = data;
+   d->host = strdup(host);
+   EINA_SAFETY_ON_NULL_GOTO(d->host, failed_host);
+   d->port = strdup(port);
+   EINA_SAFETY_ON_NULL_GOTO(d->port, failed_port);
+
+   if (!hints) d->hints = NULL;
+   else
+     {
+        d->hints = malloc(sizeof(struct addrinfo));
+        EINA_SAFETY_ON_NULL_GOTO(d->hints, failed_hints);
+        memcpy(d->hints, hints, sizeof(struct addrinfo));
+     }
+
+   d->result = NULL;
+
+   return ecore_thread_run(_efl_net_ip_resolve_async_run,
+                           _efl_net_ip_resolve_async_end,
+                           _efl_net_ip_resolve_async_cancel,
+                           d);
+
+ failed_hints:
+   free(d->port);
+ failed_port:
+   free(d->host);
+ failed_host:
+   free(d);
+   return NULL;
+}
+
 typedef struct _Efl_Net_Connect_Async_Data
 {
    Efl_Net_Connect_Async_Cb cb;
diff --git a/src/lib/ecore_con/ecore_con_private.h 
b/src/lib/ecore_con/ecore_con_private.h
index f85433b..2f98dd9 100644
--- a/src/lib/ecore_con/ecore_con_private.h
+++ b/src/lib/ecore_con/ecore_con_private.h
@@ -406,6 +406,26 @@ Eina_Bool efl_net_ip_port_split(char *buf, const char 
**p_host, const char **p_p
 int efl_net_socket4(int domain, int type, int protocol, Eina_Bool 
close_on_exec);
 
 /**
+ * @brief callback to notify of resolved address.
+ *
+ * The callback is given the ownership of the result, thus must free
+ * it with freeaddrinfo().
+ *
+ * @internal
+ */
+typedef void (*Efl_Net_Ip_Resolve_Async_Cb)(void *data, const char *host, 
const char *port, const struct addrinfo *hints, struct addrinfo *result, int 
gai_error);
+
+/**
+ * @brief asynchronously resolve a host and port using getaddrinfo().
+ *
+ * This will call getaddrinfo() in a thread, taking care to return the
+ * result to the main loop and calling @a cb with given user @a data.
+ *
+ * @internal
+ */
+Ecore_Thread *efl_net_ip_resolve_async_new(const char *host, const char *port, 
const struct addrinfo *hints, Efl_Net_Ip_Resolve_Async_Cb cb, const void *data);
+
+/**
  * @brief callback to notify of connection.
  *
  * The callback is given the ownership of the socket (sockfd), thus
diff --git a/src/lib/ecore_con/efl_net_server.eo 
b/src/lib/ecore_con/efl_net_server.eo
index da65841..3e1a9fe 100644
--- a/src/lib/ecore_con/efl_net_server.eo
+++ b/src/lib/ecore_con/efl_net_server.eo
@@ -1,3 +1,5 @@
+var Efl.Net.Server.Error.COULDNT_RESOLVE_HOST: Eina.Error; [[The server could 
not resolve the given host name or port given as address.]]
+
 interface Efl.Net.Server {
     [[The basic server interface.
 
diff --git a/src/lib/ecore_con/efl_net_server_tcp.c 
b/src/lib/ecore_con/efl_net_server_tcp.c
index 56b1faa..cdf751f 100644
--- a/src/lib/ecore_con/efl_net_server_tcp.c
+++ b/src/lib/ecore_con/efl_net_server_tcp.c
@@ -31,6 +31,7 @@
 
 typedef struct _Efl_Net_Server_Tcp_Data
 {
+   Ecore_Thread *resolver;
    Eina_Bool ipv6_only;
 } Efl_Net_Server_Tcp_Data;
 
@@ -41,85 +42,41 @@ _efl_net_server_tcp_efl_object_constructor(Eo *o, 
Efl_Net_Server_Tcp_Data *pd)
    return efl_constructor(efl_super(o, MY_CLASS));
 }
 
-EOLIAN static Eina_Error
-_efl_net_server_tcp_efl_net_server_serve(Eo *o, Efl_Net_Server_Tcp_Data *pd, 
const char *address)
+EOLIAN void
+_efl_net_server_tcp_efl_object_destructor(Eo *o, Efl_Net_Server_Tcp_Data *pd)
 {
-   struct sockaddr_storage addr = {};
-   char *str, *host, *port;
-   int r, fd;
-   socklen_t addrlen;
-   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
-   Eina_Error err = 0;
-
-   EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
-
-   // TODO: change to getaddrinfo() and move to a thread...
-   str = host = strdup(address);
-   EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
-
-   if (host[0] == '[')
-     {
-        struct sockaddr_in6 *a = (struct sockaddr_in6 *)&addr;
-        /* IPv6 is: [IP]:port */
-        host++;
-        port = strchr(host, ']');
-        if (!port)
-          {
-             ERR("missing ']' in IPv6 address: %s", address);
-             err = EINVAL;
-             goto invalid_address;
-          }
-        *port = '\0';
-        port++;
-
-        if (port[0] == ':')
-          port++;
-        else
-          port = NULL;
-        a->sin6_family = AF_INET6;
-        a->sin6_port = htons(port ? atoi(port) : 0);
-        r = inet_pton(AF_INET6, host, &(a->sin6_addr));
-        addrlen = sizeof(*a);
-     }
-   else
+   if (pd->resolver)
      {
-        struct sockaddr_in *a = (struct sockaddr_in *)&addr;
-        port = strchr(host, ':');
-        if (port)
-          {
-             *port = '\0';
-             port++;
-          }
-        a->sin_family = AF_INET;
-        a->sin_port = htons(port ? atoi(port) : 0);
-        r = inet_pton(AF_INET, host, &(a->sin_addr));
-        addrlen = sizeof(*a);
+        ecore_thread_cancel(pd->resolver);
+        pd->resolver = NULL;
      }
+   efl_destructor(efl_super(o, MY_CLASS));
+}
 
-   if (r != 1)
-     {
-        ERR("could not parse IP '%s' (%s)", host, address);
-        err = EINVAL;
-        goto invalid_address;
-     }
-   free(str);
+static Eina_Error
+_efl_net_server_tcp_resolved_bind(Eo *o, Efl_Net_Server_Tcp_Data *pd, const 
struct addrinfo *addr)
+{
+   Eina_Error err = 0;
+   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+   socklen_t addrlen = addr->ai_addrlen;
+   int fd, r;
 
-   efl_net_server_fd_family_set(o, addr.ss_family);
+   efl_net_server_fd_family_set(o, addr->ai_family);
 
-   fd = efl_net_socket4(addr.ss_family, SOCK_STREAM, IPPROTO_TCP,
+   fd = efl_net_socket4(addr->ai_family, addr->ai_socktype, addr->ai_protocol,
                         efl_net_server_fd_close_on_exec_get(o));
    if (fd < 0)
      {
-        err = errno;
-        ERR("socket(%d, SOCK_STREAM, IPPROTO_TCP): %s",
-            addr.ss_family, strerror(errno));
-        goto error_socket;
+        ERR("socket(%d, %d, %d): %s",
+            addr->ai_family, addr->ai_socktype, addr->ai_protocol,
+            strerror(errno));
+        return errno;
      }
 
    efl_loop_fd_set(o, fd);
 
    /* apply pending value BEFORE bind() */
-   if (addr.ss_family == AF_INET6)
+   if (addr->ai_family == AF_INET6)
      {
         if (pd->ipv6_only == 0xff)
           efl_net_server_tcp_ipv6_only_get(o); /* fetch & sync */
@@ -127,44 +84,102 @@ _efl_net_server_tcp_efl_net_server_serve(Eo *o, 
Efl_Net_Server_Tcp_Data *pd, con
           efl_net_server_tcp_ipv6_only_set(o, pd->ipv6_only);
      }
 
-   r = bind(fd, (struct sockaddr *)&addr, addrlen);
+   r = bind(fd, addr->ai_addr, addrlen);
    if (r < 0)
      {
         err = errno;
-        ERR("bind(%d, %s): %s", fd, address, strerror(errno));
-        goto error_listen;
-     }
-
-   if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
-     {
-        ERR("getsockname(%d): %s", fd, strerror(errno));
-        goto error_listen;
+        efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr);
+        DBG("bind(%d, %s): %s", fd, buf, strerror(errno));
+        goto error;
      }
-   else if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr))
-     efl_net_server_address_set(o, buf);
 
    r = listen(fd, 0);
    if (r < 0)
      {
         err = errno;
-        ERR("listen(%d): %s", fd, strerror(errno));
-        goto error_listen;
+        DBG("listen(%d): %s", fd, strerror(errno));
+        goto error;
+     }
+
+   if (getsockname(fd, addr->ai_addr, &addrlen) != 0)
+     {
+        ERR("getsockname(%d): %s", fd, strerror(errno));
+        goto error;
      }
+   else if (efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr))
+     efl_net_server_address_set(o, buf);
 
+   DBG("fd=%d serving at %s", fd, buf);
    efl_net_server_serving_set(o, EINA_TRUE);
    return 0;
 
- invalid_address:
-   free(str);
-   goto error_socket;
-
- error_listen:
+ error:
+   efl_net_server_fd_family_set(o, AF_UNSPEC);
+   efl_loop_fd_set(o, -1);
    close(fd);
- error_socket:
-   efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err);
    return err;
 }
 
+static void
+_efl_net_server_tcp_resolved(void *data, const char *host EINA_UNUSED, const 
char *port EINA_UNUSED, const struct addrinfo *hints EINA_UNUSED, struct 
addrinfo *result, int gai_error)
+{
+   Eo *o = data;
+   Efl_Net_Server_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS);
+   const struct addrinfo *addr;
+   Eina_Error err;
+
+   pd->resolver = NULL;
+
+   efl_ref(o); /* we're emitting callbacks then continuing the workflow */
+
+   if (gai_error)
+     {
+        err = EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST;
+        goto end;
+     }
+
+   for (addr = result; addr != NULL; addr = addr->ai_next)
+     {
+        err = _efl_net_server_tcp_resolved_bind(o, pd, addr);
+        if (err == 0) break;
+     }
+   freeaddrinfo(result);
+
+ end:
+   if (err) efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err);
+
+   efl_unref(o);
+}
+
+EOLIAN static Eina_Error
+_efl_net_server_tcp_efl_net_server_serve(Eo *o, Efl_Net_Server_Tcp_Data *pd, 
const char *address)
+{
+   char *str;
+   const char *host, *port;
+   struct addrinfo hints = {
+     .ai_socktype = SOCK_STREAM,
+     .ai_protocol = IPPROTO_TCP,
+     .ai_family = AF_UNSPEC,
+   };
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
+
+   str = strdup(address);
+   if (!efl_net_ip_port_split(str, &host, &port))
+     {
+        free(str);
+        return EINVAL;
+     }
+   if (!port) port = "0";
+   if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+   pd->resolver = efl_net_ip_resolve_async_new(host, port, &hints,
+                                               _efl_net_server_tcp_resolved, 
o);
+   free(str);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(pd->resolver, EINVAL);
+   return 0;
+}
+
 static Efl_Callback_Array_Item *_efl_net_server_tcp_client_cbs(void);
 
 static void
diff --git a/src/lib/ecore_con/efl_net_server_tcp.eo 
b/src/lib/ecore_con/efl_net_server_tcp.eo
index 45636f2..5b60a91 100644
--- a/src/lib/ecore_con/efl_net_server_tcp.eo
+++ b/src/lib/ecore_con/efl_net_server_tcp.eo
@@ -35,6 +35,7 @@ class Efl.Net.Server.Tcp (Efl.Net.Server.Fd) {
 
     implements {
         Efl.Object.constructor;
+        Efl.Object.destructor;
         Efl.Net.Server.serve;
         Efl.Net.Server.Fd.client_add;
         Efl.Net.Server.Fd.client_reject;

-- 


Reply via email to