efl_net_dialer_tcp: add SOCKS proxy support.
authorGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Mon, 19 Sep 2016 03:56:18 +0000 (00:56 -0300)
committerGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Mon, 19 Sep 2016 04:18:14 +0000 (01:18 -0300)
SOCKS is implemented in its own thread using synchronous/blocking
primitives, which simplifies the code a lot -- as well as simulate the
usage of Ecore_Thread as our users will likely do.

Since SOCKSv4a and SOCKSv5 allow name resolution, the whole
getaddrinfo() is done in the same thread, when needed, instead of a
separate thread to do that, which should also save some resources.

Instead of the legacy ECORE_CON_SOCKS_V4 and ECORE_CON_SOCKS_V5, now
we use socks_proxy, all_proxy and no_proxy. This matches our other
dialers http/websocket (which will use http_proxy, all_proxy and
no_proxy). If desired it's easy to add back support for those
variables, but I think we should just deprecate them. (The legacy code
will keep unchanged, thus direct users of ecore_con_server will still
use those -- just the previous users of ecore_con_server will be
converted to use the new API).

src/lib/ecore_con/Ecore_Con_Eo.h
src/lib/ecore_con/ecore_con.c
src/lib/ecore_con/ecore_con_private.h
src/lib/ecore_con/efl_net_dialer.eo
src/lib/ecore_con/efl_net_dialer_tcp.c

index f10d1d7..ec7a228 100644 (file)
@@ -21,6 +21,7 @@
 extern Eina_Error EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
 extern Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
 extern Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+extern Eina_Error EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED;
 
 /* TODO: should be generated from 'var Efl.Net.Http.Error.*' */
 extern Eina_Error EFL_NET_HTTP_ERROR_BAD_CONTENT_ENCODING;
index 4efcb2a..7d8c379 100644 (file)
 #include "Ecore_Con.h"
 #include "ecore_con_private.h"
 
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0 /* noop */
+#endif
+
 static Eina_Bool   _ecore_con_client_timer(Ecore_Con_Client *cl);
 static void        _ecore_con_cl_timer_update(Ecore_Con_Client *cl);
 static Eina_Bool   _ecore_con_server_timer(Ecore_Con_Server *svr);
@@ -178,6 +182,7 @@ EAPI int ECORE_CON_EVENT_PROXY_BIND = 0;
 EAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_CONNECT = 0;
 EAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY = 0;
 EAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST = 0;
+EAPI Eina_Error EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = 0;
 
 static Eina_List *servers = NULL;
 static int _ecore_con_init_count = 0;
@@ -222,6 +227,7 @@ ecore_con_init(void)
    EFL_NET_DIALER_ERROR_COULDNT_CONNECT = eina_error_msg_static_register("Couldn't connect to server");
    EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY = eina_error_msg_static_register("Couldn't resolve proxy name");
    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");
 
    eina_magic_string_set(ECORE_MAGIC_CON_SERVER, "Ecore_Con_Server");
    eina_magic_string_set(ECORE_MAGIC_CON_CLIENT, "Ecore_Con_Client");
@@ -3076,6 +3082,7 @@ efl_net_ip_port_split(char *buf, const char **p_host, const char **p_port)
           {
              *port = '\0';
              port++;
+             if (*port == '\0') port = NULL;
           }
      }
 
@@ -3084,261 +3091,1538 @@ efl_net_ip_port_split(char *buf, const char **p_host, const char **p_port)
    return EINA_TRUE;
 }
 
+static void
+_cleanup_close(void *data)
+{
+   int *p_fd = data;
+   int fd = *p_fd;
+   *p_fd = -1;
+   if (fd >= 0) close(fd);
+}
 
 int
 efl_net_socket4(int domain, int type, int protocol, Eina_Bool close_on_exec)
 {
-   int fd;
+   int fd = -1;
 
 #ifdef SOCK_CLOEXEC
    if (close_on_exec) type |= SOCK_CLOEXEC;
 #endif
 
    fd = socket(domain, type, protocol);
-   if (fd < 0) return fd;
-
 #ifndef SOCK_CLOEXEC
-   if (close_on_exec)
+   EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+   if (fd > 0)
      {
-        if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
+        if (close_on_exec)
           {
-             int errno_bkp = errno;
-             ERR("fcntl(%d, F_SETFD, FD_CLOEXEC): %s", fd, strerror(errno));
-             close(fd);
-             errno = errno_bkp;
-             return -1;
+             if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
+               {
+                  int errno_bkp = errno;
+                  ERR("fcntl(%d, F_SETFD, FD_CLOEXEC): %s", fd, strerror(errno));
+                  close(fd);
+                  fd = -1;
+                  errno = errno_bkp;
+               }
           }
      }
+   EINA_THREAD_CLEANUP_POP(EINA_FALSE); /* we need fd on success */
 #endif
 
    return fd;
 }
 
-typedef struct _Efl_Net_Resolve_Async_Data
+typedef struct _Efl_Net_Connect_Async_Data
 {
-   Efl_Net_Resolve_Async_Cb cb;
+   Efl_Net_Connect_Async_Cb cb;
    const void *data;
-   char *host;
-   char *port;
-   struct addrinfo *result;
-   struct addrinfo *hints;
-   int gai_error;
-} Efl_Net_Resolve_Async_Data;
+   socklen_t addrlen;
+   Eina_Bool close_on_exec;
+   int type;
+   int protocol;
+   int sockfd;
+   Eina_Error error;
+   struct sockaddr addr[];
+} Efl_Net_Connect_Async_Data;
 
 static void
-_efl_net_resolve_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
 {
-   Efl_Net_Resolve_Async_Data *d = data;
+   Efl_Net_Connect_Async_Data *d = data;
+   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+   int r;
 
    /* 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_resolve_async_cancel().
+    * handle that with _efl_net_connect_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;
+   d->error = 0;
 
-        DBG("getaddrinfo(\"%s\", \"%s\") failed: %s", d->host, d->port, gai_strerror(d->gai_error));
-        break;
+   /* always close-on-exec since it's not a point to pass an
+    * under construction socket to a child process.
+    */
+   d->sockfd = efl_net_socket4(d->addr->sa_family, d->type, d->protocol, EINA_TRUE);
+   if (d->sockfd < 0)
+     {
+        d->error = errno;
+        DBG("socket(%d, %d, %d) failed: %s", d->addr->sa_family, d->type, d->protocol, strerror(errno));
+        return;
      }
 
-   eina_thread_cancellable_set(EINA_FALSE, NULL);
-
    if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+     efl_net_ip_port_fmt(buf, sizeof(buf), d->addr);
+
+   DBG("connecting fd=%d to %s", d->sockfd, buf);
+
+   r = connect(d->sockfd, d->addr, d->addrlen);
+   if (r < 0)
      {
-        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);
-          }
+        int fd = d->sockfd;
+        d->error = errno;
+        d->sockfd = -1;
+        /* close() is a cancellation point, thus unset sockfd before
+         * closing, so the main thread _efl_net_connect_async_cancel()
+         * won't close it again.
+         */
+        close(fd);
+        DBG("connect(%d, %s) failed: %s", fd, buf, strerror(errno));
+        return;
      }
+
+   DBG("connected fd=%d to %s", d->sockfd, buf);
 }
 
 static void
-_efl_net_resolve_async_data_free(Efl_Net_Resolve_Async_Data *d)
+_efl_net_connect_async_data_free(Efl_Net_Connect_Async_Data *d)
 {
-   free(d->hints);
-   free(d->host);
-   free(d->port);
    free(d);
 }
 
 static void
-_efl_net_resolve_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
 {
-   Efl_Net_Resolve_Async_Data *d = data;
-   d->cb((void *)d->data, d->host, d->port, d->hints, d->result, d->gai_error);
-   _efl_net_resolve_async_data_free(d);
+   Efl_Net_Connect_Async_Data *d = data;
+
+#ifdef FD_CLOEXEC
+   /* if it wasn't a close on exec, release the socket to be passed to child */
+   if ((!d->close_on_exec) && (d->sockfd >= 0))
+     {
+        int flags = fcntl(d->sockfd, F_GETFD);
+        if (flags < 0)
+          {
+             d->error = errno;
+             ERR("fcntl(%d, F_GETFD): %s", d->sockfd, strerror(errno));
+             close(d->sockfd);
+             d->sockfd = -1;
+          }
+        else
+          {
+             flags &= (~FD_CLOEXEC);
+             if (fcntl(d->sockfd, F_SETFD, flags) < 0)
+               {
+                  d->error = errno;
+                  ERR("fcntl(%d, F_SETFD, %#x): %s", d->sockfd, flags, strerror(errno));
+                  close(d->sockfd);
+                  d->sockfd = -1;
+               }
+          }
+     }
+#endif
+   d->cb((void *)d->data, d->addr, d->addrlen, d->sockfd, d->error);
+   _efl_net_connect_async_data_free(d);
 }
 
 static void
-_efl_net_resolve_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
 {
-   Efl_Net_Resolve_Async_Data *d = data;
-   if (d->result) freeaddrinfo(d->result);
-   _efl_net_resolve_async_data_free(d);
+   Efl_Net_Connect_Async_Data *d = data;
+   if (d->sockfd >= 0) close(d->sockfd);
+   _efl_net_connect_async_data_free(d);
 }
 
 Ecore_Thread *
-efl_net_resolve_async_new(const char *host, const char *port, const struct addrinfo *hints, Efl_Net_Resolve_Async_Cb cb, const void *data)
+efl_net_connect_async_new(const struct sockaddr *addr, socklen_t addrlen, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data)
 {
-   Efl_Net_Resolve_Async_Data *d;
+   Efl_Net_Connect_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(addr, NULL);
+   EINA_SAFETY_ON_TRUE_RETURN_VAL(addrlen < 1, NULL);
    EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
 
-   d = malloc(sizeof(Efl_Net_Resolve_Async_Data));
+   d = malloc(sizeof(Efl_Net_Connect_Async_Data) + addrlen);
    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);
+   d->addrlen = addrlen;
+   d->close_on_exec = close_on_exec;
+   d->type = type;
+   d->protocol = protocol;
+   memcpy(d->addr, addr, addrlen);
 
-   if (!hints) d->hints = NULL;
-   else
+   d->sockfd = -1;
+   d->error = 0;
+
+   return ecore_thread_run(_efl_net_connect_async_run,
+                           _efl_net_connect_async_end,
+                           _efl_net_connect_async_cancel,
+                           d);
+}
+
+static Eina_Bool
+_efl_net_ip_no_proxy(const char *host, char * const *no_proxy_strv)
+{
+   char * const *itr;
+   size_t host_len;
+
+   if (!no_proxy_strv)
+     return EINA_FALSE;
+
+   host_len = strlen(host);
+   for (itr = no_proxy_strv; *itr != NULL; itr++)
      {
-        d->hints = malloc(sizeof(struct addrinfo));
-        EINA_SAFETY_ON_NULL_GOTO(d->hints, failed_hints);
-        memcpy(d->hints, hints, sizeof(struct addrinfo));
-     }
+        const char *s = *itr;
+        size_t slen;
 
-   d->result = NULL;
+        /* '*' is not a glob/pattern, it matches all */
+        if (*s == '*') return EINA_TRUE;
 
-   return ecore_thread_run(_efl_net_resolve_async_run,
-                           _efl_net_resolve_async_end,
-                           _efl_net_resolve_async_cancel,
-                           d);
+        /* old timers use leading dot to avoid matching partial names
+         * due implementation bugs not required anymore
+         */
+        if (*s == '.') s++;
 
- failed_hints:
-   free(d->port);
- failed_port:
-   free(d->host);
- failed_host:
-   free(d);
-   return NULL;
+        slen = strlen(s);
+        if (slen == 0) continue;
+
+        if (host_len < slen) continue;
+        if (memcmp(host + host_len - slen, s, slen) == 0)
+          {
+             if (slen == host_len)
+               return EINA_TRUE;
+             if (host[host_len - slen - 1] == '.')
+               return EINA_TRUE;
+          }
+     }
+
+   return EINA_FALSE;
 }
 
-typedef struct _Efl_Net_Connect_Async_Data
+typedef struct _Efl_Net_Ip_Connect_Async_Data
 {
    Efl_Net_Connect_Async_Cb cb;
    const void *data;
+   char *address;
+   char *proxy;
+   char *proxy_env;
+   char **no_proxy_strv;
    socklen_t addrlen;
    Eina_Bool close_on_exec;
    int type;
    int protocol;
    int sockfd;
    Eina_Error error;
-   struct sockaddr addr[];
-} Efl_Net_Connect_Async_Data;
+   union {
+      struct sockaddr_in addr4;
+      struct sockaddr_in6 addr6;
+      struct sockaddr addr;
+   };
+} Efl_Net_Ip_Connect_Async_Data;
+
+static Eina_Error
+_efl_net_ip_connect(const struct addrinfo *addr, int *sockfd)
+{
+   int fd = -1;
+   Eina_Error ret = 0;
+
+   /* always close-on-exec since it's not a point to pass an
+    * under construction socket to a child process.
+    */
+   fd = efl_net_socket4(addr->ai_family, addr->ai_socktype, addr->ai_protocol, EINA_TRUE);
+   if (fd < 0) ret = errno;
+   else
+     {
+        char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+        int r;
+
+        EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+        if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+          {
+             if (efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr))
+               DBG("connect fd=%d to %s", fd, buf);
+          }
+
+        r = connect(fd, addr->ai_addr, addr->ai_addrlen);
+        if (r == 0)
+          {
+             DBG("connected fd=%d to %s", fd, buf);
+             *sockfd = fd;
+          }
+        else
+          {
+             ret = errno;
+             DBG("couldn't connect fd=%d to %s: %s", fd, buf, strerror(errno));
+             close(fd);
+          }
+        EINA_THREAD_CLEANUP_POP(EINA_FALSE); /* we need sockfd on success */
+     }
+   return ret;
+}
+
+static Eina_Error
+_efl_net_ip_resolve_and_connect(const char *host, const char *port, int type, int protocol, int *sockfd, struct sockaddr *addr, socklen_t *p_addrlen)
+{
+   struct addrinfo *results = NULL;
+   struct addrinfo hints = {
+     .ai_socktype = type,
+     .ai_protocol = protocol,
+     .ai_family = AF_UNSPEC,
+   };
+   Eina_Error ret = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+   int r;
+
+   if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+   do
+     r = getaddrinfo(host, port, &hints, &results);
+   while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+
+   if (r != 0)
+     {
+        DBG("couldn't resolve host='%s', port='%s': %s",
+            host, port, gai_strerror(r));
+        ret = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+        *sockfd = -1;
+     }
+   else
+     {
+        const struct addrinfo *addrinfo;
+
+        EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+        for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+          {
+             if (addrinfo->ai_socktype != type) continue;
+             if (addrinfo->ai_protocol != protocol) continue;
+             ret = _efl_net_ip_connect(addrinfo, sockfd);
+             if (ret == 0)
+               {
+                  memcpy(addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+                  *p_addrlen = addrinfo->ai_addrlen;
+                  break;
+               }
+          }
+        if (ret != 0)
+          ret = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+        EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+     }
+   return ret;
+}
 
 static void
-_efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_ip_connect_async_run_direct(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port)
 {
-   Efl_Net_Connect_Async_Data *d = data;
-   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+   DBG("direct connection to %s:%s", host, port);
+   d->error = _efl_net_ip_resolve_and_connect(host, port, d->type, d->protocol, &d->sockfd, &d->addr, &d->addrlen);
+}
+
+static Eina_Bool
+_efl_net_ip_port_user_pass_split(char *buf, const char **p_host, const char **p_port, const char **p_user, const char **p_pass)
+{
+   char *p;
+
+   p = strchr(buf, '@');
+   if (!p)
+     {
+        p = buf;
+        *p_user = NULL;
+        *p_pass = NULL;
+     }
+   else
+     {
+        char *s;
+        *p_user = buf;
+        *p = '\0';
+        p++;
+
+        s = strchr(*p_user, ':');
+        if (!s)
+          *p_pass = NULL;
+        else
+          {
+             *s = '\0';
+             s++;
+             *p_pass = s;
+          }
+     }
+
+   return efl_net_ip_port_split(p, p_host, p_port);
+}
+
+typedef enum _Efl_Net_Socks4_Request_Command {
+  EFL_NET_SOCKS4_REQUEST_COMMAND_CONNECT = 0x01,
+  EFL_NET_SOCKS4_REQUEST_COMMAND_BIND = 0x02
+} Efl_Net_Socks4_Request_Command;
+
+typedef struct _Efl_Net_Socks4_Request {
+   uint8_t version; /* = 0x4 */
+   uint8_t command; /* Efl_Net_Socks4_Request_Command */
+   uint16_t port;
+   uint8_t ipv4[4];
+   char indent[];
+} Efl_Net_Socks4_Request;
+
+typedef enum _Efl_Net_Socks4_Reply_Status {
+  EFL_NET_SOCKS4_REPLY_STATUS_GRANTED = 0x5a,
+  EFL_NET_SOCKS4_REPLY_STATUS_REJECTED = 0x5b,
+  EFL_NET_SOCKS4_REPLY_STATUS_FAILED_INDENT = 0x5c,
+  EFL_NET_SOCKS4_REPLY_STATUS_FAILED_USER = 0x5d
+} Efl_Net_Socks4_Reply_Status;
+
+typedef struct _Efl_Net_Socks4_Reply {
+   uint8_t null;
+   uint8_t status;
+   uint16_t port;
+   uint8_t ipv4[4];
+} Efl_Net_Socks4_Reply;
+
+static Eina_Bool
+_efl_net_ip_connect_async_run_socks4_try(Efl_Net_Ip_Connect_Async_Data *d, const char *proxy_host, const char *proxy_port, const struct addrinfo *addrinfo, Efl_Net_Socks4_Request *request, size_t request_len)
+{
+   char buf[INET_ADDRSTRLEN + sizeof(":65536")];
+   struct sockaddr_in *a = (struct sockaddr_in *)addrinfo->ai_addr;
+   struct sockaddr_storage proxy_addr;
+   socklen_t proxy_addrlen;
+   int fd;
+   Eina_Error err;
+   Eina_Bool ret = EINA_FALSE;
+   ssize_t s;
+
+   err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+   if (err)
+     {
+        DBG("couldn't connect to socks4://%s:%s: %s", proxy_host, proxy_port, eina_error_msg_get(err));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        return EINA_TRUE; /* no point in continuing on this error */
+     }
+
+   EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+   if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+     {
+        if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
+          DBG("resolved address='%s' to %s. Connect using fd=%d socks4://%s:%s", d->address, buf, fd, proxy_host, proxy_port);
+     }
+
+   request->port = a->sin_port;
+   memcpy(request->ipv4, &a->sin_addr, 4);
+
+   s = send(fd, request, request_len, MSG_NOSIGNAL);
+   if (s != (ssize_t)request_len)
+     {
+        if (s < 0)
+          DBG("couldn't request connection to host=%s fd=%d socks4://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+        else
+          DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+     }
+   else
+     {
+        Efl_Net_Socks4_Reply reply;
+        s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+        if (s != sizeof(reply))
+          {
+             if (s < 0)
+               DBG("couldn't recv reply of connection to host=%s fd=%d socks4://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+             else
+               DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+          }
+        else
+          {
+             if (reply.status != EFL_NET_SOCKS4_REPLY_STATUS_GRANTED)
+               DBG("rejected connection to host=%s fd=%d socks4://%s:%s: reason=%#x", buf, fd, proxy_host, proxy_port, reply.status);
+             else
+               {
+                  memcpy(&d->addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+                  d->addrlen = addrinfo->ai_addrlen;
+                  d->sockfd = fd;
+                  d->error = 0;
+                  ret = EINA_TRUE;
+                  DBG("connected to host=%s fd=%d socks4://%s:%s", buf, fd, proxy_host, proxy_port);
+               }
+          }
+     }
+   EINA_THREAD_CLEANUP_POP(!ret); /* we need fd on success, on failure just close it */
+   return ret;
+}
+
+static void
+_efl_net_ip_connect_async_run_socks4(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+   char *str;
+   const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+   struct addrinfo *results = NULL;
+   struct addrinfo hints = {
+     .ai_socktype = d->type,
+     .ai_protocol = d->protocol,
+     .ai_family = AF_INET,
+   };
    int r;
 
-   /* 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_connect_async_cancel().
-    */
-   eina_thread_cancellable_set(EINA_TRUE, NULL);
+   if (strchr(host, ':'))
+     {
+        DBG("SOCKSv4 only handles IPv4. Wanted host=%s", host);
+        d->error = EAFNOSUPPORT;
+        return;
+     }
 
-   d->error = 0;
+   if ((d->type != SOCK_STREAM) || (d->protocol != IPPROTO_TCP))
+     {
+        DBG("SOCKSv4 only accepts TCP requests. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+        d->error = EPROTONOSUPPORT;
+        return;
+     }
 
-   d->sockfd = efl_net_socket4(d->addr->sa_family, d->type, d->protocol, d->close_on_exec);
-   if (d->sockfd < 0)
+   DBG("proxy connection to %s:%s using socks4://%s", host, port, proxy);
+
+   str = strdup(proxy);
+   EINA_THREAD_CLEANUP_PUSH(free, str);
+
+   if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
      {
-        d->error = errno;
-        DBG("socket(%d, %d, %d) failed: %s", d->addr->sa_family, d->type, d->protocol, strerror(errno));
+        ERR("Invalid proxy string: socks4://%s", proxy);
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        goto end;
+     }
+   if (!proxy_user) proxy_user = "";
+   if (!proxy_port) proxy_port = "1080";
+
+   do
+     r = getaddrinfo(host, port, &hints, &results);
+   while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+   if (r != 0)
+     {
+        DBG("couldn't resolve host='%s', port='%s': %s",
+            host, port, gai_strerror(r));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+     }
+   else
+     {
+        const struct addrinfo *addrinfo;
+        Efl_Net_Socks4_Request *request;
+        size_t request_len;
+
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+        EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+
+        request_len = sizeof(Efl_Net_Socks4_Request) + strlen(proxy_user) + 1;
+        request = malloc(request_len);
+        if (request)
+          {
+             request->version = 0x04;
+             request->command = EFL_NET_SOCKS4_REQUEST_COMMAND_CONNECT;
+             memcpy(request->indent, proxy_user, strlen(proxy_user) + 1);
+             EINA_THREAD_CLEANUP_PUSH(free, request);
+             for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+               {
+                  if (addrinfo->ai_socktype != d->type) continue;
+                  if (addrinfo->ai_protocol != d->protocol) continue;
+                  if (addrinfo->ai_family != AF_INET) continue;
+                  if (_efl_net_ip_connect_async_run_socks4_try(d, proxy_host, proxy_port, addrinfo, request, request_len))
+                    break;
+               }
+             EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(request) */
+          }
+        EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+     }
+ end:
+   EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+static void
+_efl_net_ip_connect_async_run_socks4a(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+   int fd = -1;
+   char *str;
+   const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+   struct sockaddr_storage proxy_addr;
+   socklen_t proxy_addrlen;
+   Eina_Error err;
+   struct addrinfo *results = NULL;
+   struct addrinfo hints = {
+     .ai_socktype = d->type,
+     .ai_protocol = d->protocol,
+     .ai_family = AF_INET,
+   };
+   int r;
+
+   if (strchr(host, ':'))
+     {
+        DBG("SOCKSv4 only handles IPv4. Wanted host=%s", host);
+        d->error = EAFNOSUPPORT;
         return;
      }
 
+   if ((d->type != SOCK_STREAM) || (d->protocol != IPPROTO_TCP))
+     {
+        DBG("SOCKSv4 only accepts TCP requests. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+        d->error = EPROTONOSUPPORT;
+        return;
+     }
+
+   DBG("proxy connection to %s:%s using socks4a://%s", host, port, proxy);
+
+   str = strdup(proxy);
+   EINA_THREAD_CLEANUP_PUSH(free, str);
+
+   if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
+     {
+        ERR("Invalid proxy string: socks4a://%s", proxy);
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        goto end;
+     }
+   if (!proxy_user) proxy_user = "";
+   if (!proxy_port) proxy_port = "1080";
+
+   err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+   if (err)
+     {
+        DBG("couldn't connect to socks4a://%s: %s", proxy, eina_error_msg_get(err));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        goto end;
+     }
+
+   DBG("connected fd=%d to socks4a://%s", fd, proxy);
+   EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+
+   /* we just resolve the port number here */
+   do
+     r = getaddrinfo(NULL, port, &hints, &results);
+   while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+   if (r != 0)
+     {
+        DBG("couldn't resolve port='%s': %s", port, gai_strerror(r));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+     }
+   else
+     {
+        const struct addrinfo *addrinfo;
+        Efl_Net_Socks4_Request *request;
+        size_t request_len;
+
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+        EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+
+        request_len = sizeof(Efl_Net_Socks4_Request) + strlen(proxy_user) + 1 + strlen(host) + 1;
+        request = malloc(request_len);
+        if (request)
+          {
+             request->version = 0x04;
+             request->command = EFL_NET_SOCKS4_REQUEST_COMMAND_CONNECT;
+             memcpy(request->indent, proxy_user, strlen(proxy_user) + 1);
+             memcpy(request->indent + strlen(proxy_user) + 1, host, strlen(host) + 1);
+             EINA_THREAD_CLEANUP_PUSH(free, request);
+             for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+               {
+                  struct sockaddr_in *a = (struct sockaddr_in *)addrinfo->ai_addr;
+                  ssize_t s;
+
+                  if (addrinfo->ai_socktype != d->type) continue;
+                  if (addrinfo->ai_protocol != d->protocol) continue;
+                  if (addrinfo->ai_family != AF_INET) continue;
+
+                  request->port = a->sin_port;
+                  request->ipv4[0] = 0;
+                  request->ipv4[1] = 0;
+                  request->ipv4[2] = 0;
+                  request->ipv4[3] = 255;
+
+                  s = send(fd, request, request_len, MSG_NOSIGNAL);
+                  if (s != (ssize_t)request_len)
+                    {
+                       if (s < 0)
+                         DBG("couldn't send proxy request: %s", strerror(errno));
+                       else
+                         DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+                    }
+                  else
+                    {
+                       Efl_Net_Socks4_Reply reply;
+                       s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+                       if (s != sizeof(reply))
+                         {
+                            if (s < 0)
+                              DBG("couldn't recv proxy reply: %s", strerror(errno));
+                            else
+                              DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+                         }
+                       else
+                         {
+                            if (reply.status != EFL_NET_SOCKS4_REPLY_STATUS_GRANTED)
+                              DBG("proxy rejected request status=%#x", reply.status);
+                            else
+                              {
+                                 d->addr4.sin_family = AF_INET;
+                                 d->addr4.sin_port = a->sin_port;
+                                 memcpy(&d->addr4.sin_addr, reply.ipv4, 4);
+                                 d->addrlen = sizeof(struct sockaddr_in);
+                                 d->sockfd = fd;
+                                 d->error = 0;
+                              }
+                         }
+                    }
+                  break;
+               }
+             EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(request) */
+          }
+        EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+     }
+   EINA_THREAD_CLEANUP_POP(d->sockfd == -1); /* we need fd only on success */
+ end:
+   EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+typedef enum _Efl_Net_Socks5_Auth {
+  EFL_NET_SOCKS5_AUTH_NONE = 0x00,
+  EFL_NET_SOCKS5_AUTH_GSSAPI = 0x01,
+  EFL_NET_SOCKS5_AUTH_USER_PASS = 0x02,
+  EFL_NET_SOCKS5_AUTH_FAILED = 0xff
+} Efl_Net_Socks5_Auth;
+
+typedef struct _Efl_Net_Socks5_Greeting {
+   uint8_t version; /* = 0x5 */
+   uint8_t auths_count;
+   uint8_t auths[1]; /* series of Efl_Net_Socks5_Auth */
+} Efl_Net_Socks5_Greeting;
+
+typedef struct _Efl_Net_Socks5_Greeting_Reply {
+   uint8_t version; /* = 0x5 */
+   uint8_t auth; /* Efl_Net_Socks5_Auth */
+} Efl_Net_Socks5_Greeting_Reply;
+
+typedef enum _Efl_Net_Socks5_Request_Command {
+  EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_CONNECT = 0x01,
+  EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_BIND = 0x02,
+  EFL_NET_SOCKS5_REQUEST_COMMAND_UDP_ASSOCIATE = 0x03
+} Efl_Net_Socks5_Request_Command;
+
+typedef enum _Efl_Net_Socks5_Address_Type {
+  EFL_NET_SOCKS5_ADDRESS_TYPE_IPV4 = 0x01,
+  EFL_NET_SOCKS5_ADDRESS_TYPE_NAME = 0x03,
+  EFL_NET_SOCKS5_ADDRESS_TYPE_IPV6 = 0x04
+} Efl_Net_Socks5_Address_Type;
+
+typedef struct _Efl_Net_Socks5_Request {
+   uint8_t version; /* = 0x5 */
+   uint8_t command; /* Efl_Net_Socks5_Command */
+   uint8_t reserved;
+   uint8_t address_type; /* Efl_Net_Socks5_Address_Type */
+   /* follows:
+    *  - one of:
+    *    - 4 bytes for IPv4
+    *    - 16 bytes for IPv6
+    *    - 1 byte (size) + N bytes of name
+    *  - uint16_t port
+    */
+} Efl_Net_Socks5_Request;
+
+typedef struct _Efl_Net_Socks5_Address_Ipv4 {
+   uint8_t address[4];
+   uint16_t port;
+} Efl_Net_Socks5_Address_Ipv4;
+
+typedef struct _Efl_Net_Socks5_Address_Ipv6 {
+   uint8_t address[16];
+   uint16_t port;
+} Efl_Net_Socks5_Address_Ipv6;
+
+typedef struct _Efl_Net_Socks5_Request_Ipv4 {
+   Efl_Net_Socks5_Request base;
+   Efl_Net_Socks5_Address_Ipv4 ipv4;
+} Efl_Net_Socks5_Request_Ipv4;
+
+ typedef struct _Efl_Net_Socks5_Request_Ipv6 {
+   Efl_Net_Socks5_Request base;
+   Efl_Net_Socks5_Address_Ipv6 ipv6;
+} Efl_Net_Socks5_Request_Ipv6;
+
+static Efl_Net_Socks5_Request *
+efl_net_socks5_request_addr_new(Efl_Net_Socks5_Request_Command command, const struct sockaddr *addr, size_t *p_request_len)
+{
+   if (addr->sa_family == AF_INET)
+     {
+        const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
+        Efl_Net_Socks5_Request_Ipv4 *request;
+
+        *p_request_len = sizeof(Efl_Net_Socks5_Request_Ipv4);
+        request = malloc(*p_request_len);
+        EINA_SAFETY_ON_NULL_RETURN_VAL(request, NULL);
+        request->base.version = 0x05;
+        request->base.command = command;
+        request->base.reserved = 0;
+        request->base.address_type = EFL_NET_SOCKS5_ADDRESS_TYPE_IPV4;
+        memcpy(request->ipv4.address, &a->sin_addr, 4);
+        request->ipv4.port = a->sin_port;
+        return &request->base;
+     }
+   else
+     {
+        const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
+        Efl_Net_Socks5_Request_Ipv6 *request;
+
+        *p_request_len = sizeof(Efl_Net_Socks5_Request_Ipv6);
+        request = malloc(*p_request_len);
+        EINA_SAFETY_ON_NULL_RETURN_VAL(request, NULL);
+        request->base.version = 0x05;
+        request->base.command = command;
+        request->base.reserved = 0;
+        request->base.address_type = EFL_NET_SOCKS5_ADDRESS_TYPE_IPV6;
+        memcpy(request->ipv6.address, &a->sin6_addr, 16);
+        request->ipv6.port = a->sin6_port;
+        return &request->base;
+     }
+}
+
+/* port must be network endianess */
+static Efl_Net_Socks5_Request *
+efl_net_socks5_request_name_new(Efl_Net_Socks5_Request_Command command, const char *name, uint16_t port, size_t *p_request_len)
+{
+   Efl_Net_Socks5_Request *request;
+   uint8_t namelen = strlen(name);
+   uint8_t *p;
+
+   *p_request_len = sizeof(Efl_Net_Socks5_Request) + 1 + namelen + 2;
+   request = malloc(*p_request_len);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(request, NULL);
+   request->version = 0x05;
+   request->command = command;
+   request->reserved = 0;
+   request->address_type = EFL_NET_SOCKS5_ADDRESS_TYPE_NAME;
+
+   p = (uint8_t *)request + sizeof(Efl_Net_Socks5_Request);
+   *p = namelen;
+   p++;
+   memcpy(p, name, namelen);
+   p += namelen;
+
+   memcpy(p, &port, sizeof(port));
+   return request;
+}
+
+typedef enum _Efl_Net_Socks5_Reply_Status {
+  EFL_NET_SOCKS5_REPLY_STATUS_GRANTED = 0x00,
+  EFL_NET_SOCKS5_REPLY_STATUS_GENERAL_FAILURE = 0x01,
+  EFL_NET_SOCKS5_REPLY_STATUS_REJECTED_BY_RULESET = 0x02,
+  EFL_NET_SOCKS5_REPLY_STATUS_NETWORK_UNREACHABLE = 0x03,
+  EFL_NET_SOCKS5_REPLY_STATUS_HOST_UNREACHABLE = 0x04,
+  EFL_NET_SOCKS5_REPLY_STATUS_CONNECTION_REFUSED = 0x05,
+  EFL_NET_SOCKS5_REPLY_STATUS_TTL_EXPIRED = 0x06,
+  EFL_NET_SOCKS5_REPLY_STATUS_PROTOCOL_ERROR = 0x07,
+  EFL_NET_SOCKS5_REPLY_STATUS_ADDRESS_TYPE_UNSUPORTED = 0x08,
+} Efl_Net_Socks5_Reply_Status;
+
+typedef struct _Efl_Net_Socks5_Reply {
+   uint8_t version; /* = 0x5 */
+   uint8_t status;
+   uint8_t null; /* = 0 */
+   uint8_t address_type; /* Efl_Net_Socks5_Address_Type */
+   /* follows:
+    *  - one of:
+    *    - 4 bytes for IPv4
+    *    - 16 bytes for IPv6
+    *    - 1 byte (size) + N bytes name
+    *  - uint16_t port
+    */
+} Efl_Net_Socks5_Reply;
+
+typedef struct _Efl_Net_Socks5_Reply_Ipv4 {
+   Efl_Net_Socks5_Reply base;
+   Efl_Net_Socks5_Address_Ipv4 ipv4;
+} Efl_Net_Socks5_Reply_Ipv4;
+
+ typedef struct _Efl_Net_Socks5_Reply_Ipv6 {
+   Efl_Net_Socks5_Reply base;
+   Efl_Net_Socks5_Address_Ipv6 ipv6;
+} Efl_Net_Socks5_Reply_Ipv6;
+
+static Eina_Bool
+_efl_net_ip_connect_async_run_socks5_auth_user_pass(int fd, const char *user, const char *pass, const char *proxy_protocol, const char *proxy_host, const char *proxy_port)
+{
+   uint8_t user_len = user ? strlen(user) : 0;
+   uint8_t pass_len = pass ? strlen(pass) : 0;
+   size_t len = 1 + 1 + user_len + 1 + pass_len;
+   char *msg;
+   Eina_Bool ret = EINA_FALSE;
+   ssize_t s;
+
+   msg = malloc(len);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(msg, EINA_FALSE);
+   EINA_THREAD_CLEANUP_PUSH(free, msg);
+
+   msg[0] = 0x01; /* version */
+   msg[1] = user_len;
+   if (user) memcpy(msg + 1 + 1, user, user_len);
+   msg[1 + 1 + user_len] = pass_len;
+   if (pass) memcpy(msg + 1 + 1 + user_len + 1, pass, pass_len);
+
+   DBG("authenticate user='%s' pass=%hhu (bytes) to proxy %s://%s:%s", user, pass_len, proxy_protocol, proxy_host, proxy_port);
+
+   s = send(fd, msg, len, MSG_NOSIGNAL);
+   if (s != (ssize_t)len)
+     {
+        if (s < 0)
+          DBG("couldn't send user-password authentication to fd=%d %s://%s:%s: %s", fd, proxy_protocol, proxy_host, proxy_port, strerror(errno));
+        else
+          DBG("couldn't send user-password authentication: need %zu, did %zd", len, s);
+     }
+   else
+     {
+        uint8_t reply[2];
+
+        s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+        if (s != (ssize_t)sizeof(reply))
+          {
+             if (s < 0)
+               DBG("couldn't recv user-password authentication reply from fd=%d %s://%s:%s: %s", fd, proxy_protocol, proxy_host, proxy_port, strerror(errno));
+             else
+               DBG("couldn't recv user-password authentication reply: need %zu, did %zd", len, s);
+          }
+        else
+          {
+             if (reply[1] != 0)
+               DBG("proxy authentication failed user='%s' pass=%hhu (bytes) to proxy %s://%s:%s: reason=%#x", user, pass_len, proxy_protocol, proxy_host, proxy_port, reply[1]);
+             else
+               {
+                DBG("successfully authenticated user=%s with proxy fd=%d %s://%s:%s", user, fd, proxy_protocol, proxy_host, proxy_port);
+                ret = EINA_TRUE;
+             }
+          }
+     }
+
+   EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+
+   return ret;
+}
+
+static Eina_Bool
+_efl_net_ip_connect_async_run_socks5_try(Efl_Net_Ip_Connect_Async_Data *d, const char *proxy_host, const char *proxy_port, const char *proxy_user, const char *proxy_pass, Efl_Net_Socks5_Request_Command cmd, const struct addrinfo *addrinfo)
+{
+   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+   Efl_Net_Socks5_Greeting greeting = {
+     .version = 0x05,
+     .auths_count = 1,
+     .auths = { proxy_user ? EFL_NET_SOCKS5_AUTH_USER_PASS : EFL_NET_SOCKS5_AUTH_NONE },
+   };
+   struct sockaddr_storage proxy_addr;
+   socklen_t proxy_addrlen;
+   int fd;
+   Eina_Error err;
+   Eina_Bool ret = EINA_FALSE;
+   ssize_t s;
+
+   err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+   if (err)
+     {
+        DBG("couldn't connect to socks5://%s:%s: %s", proxy_host, proxy_port, eina_error_msg_get(err));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        return EINA_TRUE; /* no point in continuing on this error */
+     }
+
+   EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
    if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
-     efl_net_ip_port_fmt(buf, sizeof(buf), d->addr);
+     {
+        if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
+          DBG("resolved address='%s' to %s. Connect using fd=%d socks5://%s:%s", d->address, buf, fd, proxy_host, proxy_port);
+     }
 
-   DBG("connecting fd=%d to %s", d->sockfd, buf);
+   s = send(fd, &greeting, sizeof(greeting), MSG_NOSIGNAL);
+   if (s != (ssize_t)sizeof(greeting))
+     {
+        if (s < 0)
+          DBG("couldn't request connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+        else
+          DBG("couldn't send proxy request: need %zu, did %zd", sizeof(greeting), s);
+     }
+   else
+     {
+        Efl_Net_Socks5_Greeting_Reply greeting_reply;
+        s = recv(fd, &greeting_reply, sizeof(greeting_reply), MSG_NOSIGNAL);
+        if (s != sizeof(greeting_reply))
+          {
+             if (s < 0)
+               DBG("couldn't recv greeting reply of connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+             else
+               DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(greeting_reply), s);
+          }
+        else
+          {
+             if (greeting_reply.auth != greeting.auths[0])
+               DBG("proxy server rejected authentication %#x trying connection to host=%s fd=%d socks5://%s:%s", greeting.auths[0], buf, fd, proxy_host, proxy_port);
+             else
+               {
+                  if ((greeting_reply.auth == EFL_NET_SOCKS5_AUTH_USER_PASS) &&
+                      (!_efl_net_ip_connect_async_run_socks5_auth_user_pass(fd, proxy_user, proxy_pass, "socks5", proxy_host, proxy_port)))
+                    {
+                       d->error = EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED;
+                    }
+                  else
+                    {
+                       Efl_Net_Socks5_Request *request;
+                       size_t request_len;
+
+                       request = efl_net_socks5_request_addr_new(cmd, addrinfo->ai_addr, &request_len);
+                       if (request)
+                         {
+                            EINA_THREAD_CLEANUP_PUSH(free, request);
+
+                            s = send(fd, request, request_len, MSG_NOSIGNAL);
+                            if (s != (ssize_t)request_len)
+                              {
+                                 if (s < 0)
+                                   DBG("couldn't request connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+                                 else
+                                   DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+                              }
+                            else if (addrinfo->ai_family == AF_INET)
+                              {
+                                 Efl_Net_Socks5_Reply_Ipv4 reply;
+                                 s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+                                 if (s != sizeof(reply))
+                                   {
+                                      if (s < 0)
+                                        DBG("couldn't recv reply of connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+                                      else
+                                        DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+                                   }
+                                 else
+                                   {
+                                      if (reply.base.status != EFL_NET_SOCKS5_REPLY_STATUS_GRANTED)
+                                        DBG("rejected IPv4 connection to host=%s fd=%d socks5://%s:%s: reason=%#x", buf, fd, proxy_host, proxy_port, reply.base.status);
+                                      else
+                                        {
+                                           memcpy(&d->addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+                                           d->addrlen = addrinfo->ai_addrlen;
+                                           d->sockfd = fd;
+                                           d->error = 0;
+                                           ret = EINA_TRUE;
+                                           DBG("connected IPv4 to host=%s fd=%d socks5://%s:%s", buf, fd, proxy_host, proxy_port);
+                                        }
+                                   }
+                              }
+                            else if (addrinfo->ai_family == AF_INET6)
+                              {
+                                 Efl_Net_Socks5_Reply_Ipv6 reply;
+                                 s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+                                 if (s != sizeof(reply))
+                                   {
+                                      if (s < 0)
+                                        DBG("couldn't recv reply of connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+                                      else
+                                        DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+                                   }
+                                 else
+                                   {
+                                      if (reply.base.status != EFL_NET_SOCKS5_REPLY_STATUS_GRANTED)
+                                        DBG("rejected IPv6 connection to host=%s fd=%d socks5://%s:%s: reason=%#x", buf, fd, proxy_host, proxy_port, reply.base.status);
+                                      else
+                                        {
+                                           memcpy(&d->addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+                                           d->addrlen = addrinfo->ai_addrlen;
+                                           d->sockfd = fd;
+                                           d->error = 0;
+                                           ret = EINA_TRUE;
+                                           DBG("connected IPv6 to host=%s fd=%d socks5://%s:%s", buf, fd, proxy_host, proxy_port);
+                                        }
+                                   }
+                              }
+                            EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+                         }
+                    }
+               }
+          }
+     }
+   EINA_THREAD_CLEANUP_POP(!ret); /* we need fd on success, on failure just close it */
+   return ret;
+}
 
-   r = connect(d->sockfd, d->addr, d->addrlen);
-   if (r < 0)
+static void
+_efl_net_ip_connect_async_run_socks5(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+   char *str;
+   const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+   struct addrinfo *results = NULL;
+   struct addrinfo hints = {
+     .ai_socktype = d->type,
+     .ai_protocol = d->protocol,
+     .ai_family = AF_UNSPEC,
+   };
+   Efl_Net_Socks5_Request_Command cmd;
+   int r;
+
+   if ((d->type == SOCK_STREAM) && (d->protocol == IPPROTO_TCP))
+     cmd = EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_CONNECT;
+   else
+     {
+        DBG("EFL SOCKSv5 only accepts TCP requests at this moment. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+        d->error = EPROTONOSUPPORT;
+        return;
+     }
+
+   if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+   DBG("proxy connection to %s:%s using socks5://%s", host, port, proxy);
+
+   str = strdup(proxy);
+   EINA_THREAD_CLEANUP_PUSH(free, str);
+
+   if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
+     {
+        ERR("Invalid proxy string: socks5://%s", proxy);
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        goto end;
+     }
+   if (!proxy_port) proxy_port = "1080";
+
+   do
+     r = getaddrinfo(host, port, &hints, &results);
+   while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+   if (r != 0)
+     {
+        DBG("couldn't resolve host='%s', port='%s': %s",
+            host, port, gai_strerror(r));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+     }
+   else
+     {
+        const struct addrinfo *addrinfo;
+
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+        EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+        for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+          {
+             if (addrinfo->ai_socktype != d->type) continue;
+             if (addrinfo->ai_protocol != d->protocol) continue;
+             if (_efl_net_ip_connect_async_run_socks5_try(d, proxy_host, proxy_port, proxy_user, proxy_pass, cmd, addrinfo))
+               break;
+          }
+        EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+     }
+ end:
+   EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+static void
+_efl_net_ip_connect_async_run_socks5h(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+   int fd = -1;
+   char *str;
+   const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+   struct sockaddr_storage proxy_addr;
+   socklen_t proxy_addrlen;
+   Eina_Error err;
+   Efl_Net_Socks5_Greeting greeting = {
+     .version = 0x05,
+     .auths_count = 1,
+   };
+   Efl_Net_Socks5_Request_Command cmd;
+   ssize_t s;
+   int r;
+
+   if ((d->type == SOCK_STREAM) && (d->protocol == IPPROTO_TCP))
+     cmd = EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_CONNECT;
+   else
+     {
+        DBG("EFL SOCKSv5 only accepts TCP requests at this moment. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+        d->error = EPROTONOSUPPORT;
+        return;
+     }
+
+   DBG("proxy connection to %s:%s using socks5h://%s", host, port, proxy);
+
+   str = strdup(proxy);
+   EINA_THREAD_CLEANUP_PUSH(free, str);
+
+   if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
+     {
+        ERR("Invalid proxy string: socks5h://%s", proxy);
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        goto end;
+     }
+   if (!proxy_port) proxy_port = "1080";
+
+   greeting.auths[0] = proxy_user ? EFL_NET_SOCKS5_AUTH_USER_PASS : EFL_NET_SOCKS5_AUTH_NONE;
+
+   err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+   if (err)
+     {
+        DBG("couldn't connect to socks5h://%s: %s", proxy, eina_error_msg_get(err));
+        d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+        goto end;
+     }
+
+   DBG("connected fd=%d to socks5h://%s", fd, proxy);
+   EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+
+   s = send(fd, &greeting, sizeof(greeting), MSG_NOSIGNAL);
+   if (s != (ssize_t)sizeof(greeting))
+     {
+        if (s < 0)
+          DBG("couldn't request connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+        else
+          DBG("couldn't send proxy request: need %zu, did %zd", sizeof(greeting), s);
+     }
+   else
+     {
+        Efl_Net_Socks5_Greeting_Reply greeting_reply;
+        s = recv(fd, &greeting_reply, sizeof(greeting_reply), MSG_NOSIGNAL);
+        if (s != sizeof(greeting_reply))
+          {
+             if (s < 0)
+               DBG("couldn't recv greeting reply of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+             else
+               DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(greeting_reply), s);
+          }
+        else
+          {
+             if (greeting_reply.auth != greeting.auths[0])
+               DBG("proxy server rejected authentication %#x trying connection to host=%s:%s fd=%d socks5h://%s:%s", greeting.auths[0], host, port, fd, proxy_host, proxy_port);
+             else
+               {
+                  if ((greeting_reply.auth == EFL_NET_SOCKS5_AUTH_USER_PASS) &&
+                      (!_efl_net_ip_connect_async_run_socks5_auth_user_pass(fd, proxy_user, proxy_pass, "socks5h", proxy_host, proxy_port)))
+                    {
+                       d->error = EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED;
+                    }
+                  else
+                    {
+                       struct addrinfo *results = NULL;
+                       struct addrinfo hints = {
+                         .ai_socktype = d->type,
+                         .ai_protocol = d->protocol,
+                         .ai_family = AF_UNSPEC,
+                       };
+
+                       if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+                       /* we just resolve the port number here */
+                       do
+                         r = getaddrinfo(NULL, port, &hints, &results);
+                       while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+                       if (r != 0)
+                         {
+                            DBG("couldn't resolve port='%s': %s", port, gai_strerror(r));
+                            d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+                         }
+                       else
+                         {
+                            const struct addrinfo *addrinfo;
+
+                            d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+                            EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+                            for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+                              {
+                                 Efl_Net_Socks5_Request *request;
+                                 size_t request_len;
+                                 uint16_t port_num;
+
+                                 if (addrinfo->ai_socktype != d->type) continue;
+                                 if (addrinfo->ai_protocol != d->protocol) continue;
+
+                                 if (addrinfo->ai_family == AF_INET)
+                                   port_num = ((const struct sockaddr_in *)addrinfo->ai_addr)->sin_port;
+                                 else
+                                   port_num = ((const struct sockaddr_in6 *)addrinfo->ai_addr)->sin6_port;
+
+                                 request = efl_net_socks5_request_name_new(cmd, host, port_num, &request_len);
+                                 if (request)
+                                   {
+                                      EINA_THREAD_CLEANUP_PUSH(free, request);
+
+                                      s = send(fd, request, request_len, MSG_NOSIGNAL);
+                                      if (s != (ssize_t)request_len)
+                                        {
+                                           if (s < 0)
+                                             DBG("couldn't request connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+                                           else
+                                             DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+                                        }
+                                      else
+                                        {
+                                           Efl_Net_Socks5_Reply reply;
+
+                                           s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+                                           if (s != sizeof(reply))
+                                             {
+                                                if (s < 0)
+                                                  DBG("couldn't recv reply of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+                                                else
+                                                  DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+                                             }
+                                           else
+                                             {
+                                                if (reply.status != EFL_NET_SOCKS5_REPLY_STATUS_GRANTED)
+                                                  DBG("rejected connection to host=%s:%s fd=%d socks5h://%s:%s: reason=%#x", host, port, fd, proxy_host, proxy_port, reply.status);
+                                                else if (reply.address_type == EFL_NET_SOCKS5_ADDRESS_TYPE_IPV4)
+                                                  {
+                                                     Efl_Net_Socks5_Address_Ipv4 ipv4;
+
+                                                     s = recv(fd, &ipv4, sizeof(ipv4), MSG_NOSIGNAL);
+                                                     if (s != sizeof(ipv4))
+                                                       {
+                                                          if (s < 0)
+                                                            DBG("couldn't recv ipv4 of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+                                                          else
+                                                            DBG("couldn't recv proxy ipv4: need %zu, did %zd", sizeof(ipv4), s);
+                                                       }
+                                                     else
+                                                       {
+                                                          d->addr4.sin_family = AF_INET;
+                                                          d->addr4.sin_port = ipv4.port;
+                                                          memcpy(&d->addr4.sin_addr, ipv4.address, 4);
+                                                          d->addrlen = sizeof(struct sockaddr_in);
+                                                          d->sockfd = fd;
+                                                          d->error = 0;
+                                                          DBG("connected IPv4 to host=%s:%s fd=%d socks5h://%s:%s", host, port, fd, proxy_host, proxy_port);
+                                                       }
+                                                  }
+                                                else if (reply.address_type == EFL_NET_SOCKS5_ADDRESS_TYPE_IPV6)
+                                                  {
+                                                     Efl_Net_Socks5_Address_Ipv6 ipv6;
+
+                                                     s = recv(fd, &ipv6, sizeof(ipv6), MSG_NOSIGNAL);
+                                                     if (s != sizeof(ipv6))
+                                                       {
+                                                          if (s < 0)
+                                                            DBG("couldn't recv ipv6 of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+                                                          else
+                                                            DBG("couldn't recv proxy ipv6: need %zu, did %zd", sizeof(ipv6), s);
+                                                       }
+                                                     else
+                                                       {
+                                                          d->addr6.sin6_family = AF_INET;
+                                                          d->addr6.sin6_port = ipv6.port;
+                                                          memcpy(&d->addr6.sin6_addr, ipv6.address, 16);
+                                                          d->addrlen = sizeof(struct sockaddr_in);
+                                                          d->sockfd = fd;
+                                                          d->error = 0;
+                                                          DBG("connected IPv6 to host=%s:%s fd=%d socks5h://%s:%s", host, port, fd, proxy_host, proxy_port);
+                                                       }
+                                                  }
+                                                else
+                                                  {
+                                                     /* most proxy servers will return a failure instead of this, but let's guard and log */
+                                                     DBG("couldn't resolve host %s:%s fd=%d socks5h://%s:%s",  host, port, fd, proxy_host, proxy_port);
+                                                  }
+                                             }
+                                        }
+
+                                      EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(request) */
+                                      break;
+                                   }
+                              }
+                            EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+                         }
+                    }
+               }
+          }
+     }
+   EINA_THREAD_CLEANUP_POP(d->sockfd == -1); /* we need fd only on success */
+ end:
+   EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+static void
+_efl_net_ip_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+   Efl_Net_Ip_Connect_Async_Data *d = data;
+   const char *host, *port, *proxy;
+   char *addrcopy;
+
+   addrcopy = strdup(d->address);
+   if (!addrcopy)
      {
-        int fd = d->sockfd;
         d->error = errno;
-        d->sockfd = -1;
-        /* close() is a cancellation point, thus unset sockfd before
-         * closing, so the main thread _efl_net_connect_async_cancel()
-         * won't close it again.
-         */
-        close(fd);
-        DBG("connect(%d, %s) failed: %s", fd, buf, strerror(errno));
         return;
      }
 
-   DBG("connected fd=%d to %s", d->sockfd, buf);
+   if (!efl_net_ip_port_split(addrcopy, &host, &port))
+     {
+        d->error = EINVAL;
+        free(addrcopy);
+        return;
+     }
+   if (!port) port = "0";
+
+   proxy = d->proxy;
+   if (!proxy)
+     {
+        proxy = d->proxy_env;
+        if (!proxy)
+          proxy = "";
+        else
+          {
+             if (_efl_net_ip_no_proxy(host, d->no_proxy_strv))
+               proxy = "";
+          }
+     }
+
+   EINA_THREAD_CLEANUP_PUSH(free, addrcopy);
+   /* allows ecore_thread_cancel() to cancel at some points, see
+    * man:pthreads(7).
+    */
+   eina_thread_cancellable_set(EINA_TRUE, NULL);
+
+   if (!proxy[0])
+     _efl_net_ip_connect_async_run_direct(d, host, port);
+   else if (eina_str_has_prefix(proxy, "socks4://"))
+     _efl_net_ip_connect_async_run_socks4(d, host, port, proxy + strlen("socks4://"));
+   else if (eina_str_has_prefix(proxy, "socks5://"))
+     _efl_net_ip_connect_async_run_socks5(d, host, port, proxy + strlen("socks5://"));
+   else if (eina_str_has_prefix(proxy, "socks4a://"))
+     _efl_net_ip_connect_async_run_socks4a(d, host, port, proxy + strlen("socks4a://"));
+   else if (eina_str_has_prefix(proxy, "socks5h://"))
+     _efl_net_ip_connect_async_run_socks5h(d, host, port, proxy + strlen("socks5h://"));
+   else if (!strstr(proxy, "://"))
+     {
+        _efl_net_ip_connect_async_run_socks5(d, host, port, proxy);
+        if (d->error)
+          _efl_net_ip_connect_async_run_socks4(d, host, port, proxy);
+     }
+   else
+     {
+        if (d->proxy)
+          {
+             d->error = ENOTSUP;
+             ERR("proxy protocol not supported '%s'", proxy);
+          }
+        else
+          {
+             /* maybe bogus envvar, ignore it */
+             WRN("proxy protocol not supported '%s', connect directly", proxy);
+             _efl_net_ip_connect_async_run_direct(d, host, port);
+          }
+     }
+
+   if ((d->error) && (!d->proxy) && (proxy[0] != '\0'))
+     {
+        WRN("error using proxy '%s' from environment, try direct connect", proxy);
+        _efl_net_ip_connect_async_run_direct(d, host, port);
+     }
+
+   eina_thread_cancellable_set(EINA_FALSE, NULL);
+   EINA_THREAD_CLEANUP_POP(EINA_TRUE);
 }
 
 static void
-_efl_net_connect_async_data_free(Efl_Net_Connect_Async_Data *d)
+_efl_net_ip_connect_async_data_free(Efl_Net_Ip_Connect_Async_Data *d)
 {
+   free(d->address);
+   free(d->proxy);
+   free(d->proxy_env);
+   if (d->no_proxy_strv)
+     {
+        free(d->no_proxy_strv[0]);
+        free(d->no_proxy_strv);
+     }
    free(d);
 }
 
 static void
-_efl_net_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_ip_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
 {
-   Efl_Net_Connect_Async_Data *d = data;
-   d->cb((void *)d->data, d->addr, d->addrlen, d->sockfd, d->error);
-   _efl_net_connect_async_data_free(d);
+   Efl_Net_Ip_Connect_Async_Data *d = data;
+
+#ifdef FD_CLOEXEC
+   /* if it wasn't a close on exec, release the socket to be passed to child */
+   if ((!d->close_on_exec) && (d->sockfd >= 0))
+     {
+        int flags = fcntl(d->sockfd, F_GETFD);
+        if (flags < 0)
+          {
+             d->error = errno;
+             ERR("fcntl(%d, F_GETFD): %s", d->sockfd, strerror(errno));
+             close(d->sockfd);
+             d->sockfd = -1;
+          }
+        else
+          {
+             flags &= (~FD_CLOEXEC);
+             if (fcntl(d->sockfd, F_SETFD, flags) < 0)
+               {
+                  d->error = errno;
+                  ERR("fcntl(%d, F_SETFD, %#x): %s", d->sockfd, flags, strerror(errno));
+                  close(d->sockfd);
+                  d->sockfd = -1;
+               }
+          }
+     }
+#endif
+
+   d->cb((void *)d->data, &d->addr, d->addrlen, d->sockfd, d->error);
+   _efl_net_ip_connect_async_data_free(d);
 }
 
 static void
-_efl_net_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_ip_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
 {
-   Efl_Net_Connect_Async_Data *d = data;
+   Efl_Net_Ip_Connect_Async_Data *d = data;
    if (d->sockfd >= 0) close(d->sockfd);
-   _efl_net_connect_async_data_free(d);
+   _efl_net_ip_connect_async_data_free(d);
 }
 
 Ecore_Thread *
-efl_net_connect_async_new(const struct sockaddr *addr, socklen_t addrlen, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data)
+efl_net_ip_connect_async_new(const char *address, const char *proxy, const char *proxy_env, const char *no_proxy_env, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data)
 {
-   Efl_Net_Connect_Async_Data *d;
+   Efl_Net_Ip_Connect_Async_Data *d;
 
-   EINA_SAFETY_ON_NULL_RETURN_VAL(addr, NULL);
-   EINA_SAFETY_ON_TRUE_RETURN_VAL(addrlen < 1, NULL);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(address, NULL);
    EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
 
-   d = malloc(sizeof(Efl_Net_Connect_Async_Data) + addrlen);
+   d = calloc(1, sizeof(Efl_Net_Ip_Connect_Async_Data));
    EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
 
+   d->address = strdup(address);
+   EINA_SAFETY_ON_NULL_GOTO(d->address, error_address);
+
+   if (proxy)
+     {
+        d->proxy = strdup(proxy);
+        EINA_SAFETY_ON_NULL_GOTO(d->proxy, error_proxy);
+     }
+   if (proxy_env)
+     {
+        d->proxy_env = strdup(proxy_env);
+        EINA_SAFETY_ON_NULL_GOTO(d->proxy_env, error_proxy_env);
+     }
+   if (no_proxy_env)
+     {
+        d->no_proxy_strv = eina_str_split(no_proxy_env, ",", 0);
+        EINA_SAFETY_ON_NULL_GOTO(d->no_proxy_strv, error_no_proxy_strv);
+     }
+
    d->cb = cb;
    d->data = data;
-   d->addrlen = addrlen;
+   d->addrlen = 0;
    d->close_on_exec = close_on_exec;
    d->type = type;
    d->protocol = protocol;
-   memcpy(d->addr, addr, addrlen);
 
    d->sockfd = -1;
    d->error = 0;
 
-   return ecore_thread_run(_efl_net_connect_async_run,
-                           _efl_net_connect_async_end,
-                           _efl_net_connect_async_cancel,
+   return ecore_thread_run(_efl_net_ip_connect_async_run,
+                           _efl_net_ip_connect_async_end,
+                           _efl_net_ip_connect_async_cancel,
                            d);
+
+ error_no_proxy_strv:
+   free(d->proxy_env);
+ error_proxy_env:
+   free(d->proxy);
+ error_proxy:
+   free(d->address);
+ error_address:
+   free(d);
+   return NULL;
 }
index 94493b0..6437e83 100644 (file)
@@ -399,26 +399,6 @@ 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_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_resolve_async_new(const char *host, const char *port, const struct addrinfo *hints, Efl_Net_Resolve_Async_Cb cb, const void *data);
-
-/**
  * @brief callback to notify of connection.
  *
  * The callback is given the ownership of the socket (sockfd), thus
@@ -435,10 +415,46 @@ typedef void (*Efl_Net_Connect_Async_Cb)(void *data, const struct sockaddr *addr
  * return the result to the main loop and calling @a cb with given
  * user @a data.
  *
+ * For name resolution and proxy support use
+ * efl_net_ip_connect_async_new()
+ *
  * @internal
  */
 Ecore_Thread *efl_net_connect_async_new(const struct sockaddr *addr, socklen_t addrlen, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data);
 
+/**
+ * @brief asynchronously create a socket and connect to the IP address.
+ *
+ * This wil resolve the address using getaddrinfo(), create a socket
+ * and connect in a thread.
+ *
+ * If a @a proxy is given, then it's always used. Otherwise the
+ * environment variable @a proxy_env is used unless it matches @a
+ * no_proxy_env. Some systems may do special queries for proxy from
+ * the thread.
+ *
+ * @param address the host:port to connect. Host may be a name or an
+ *        IP address, IPv6 addresses should be enclosed in braces.
+ * @param proxy a mandatory proxy to use. If "" (empty string), it's
+ *        disabled. If NULL, then @a proxy_env is used unless it
+ *        matches @a no_proxy_env.
+ * @param proxy_env if @a proxy is NULL, then this will be used as the
+ *        proxy unless it matches @a no_proxy_env.
+ * @param no_proxy_env a comma-separated list of matches that will
+ *        avoid using @a proxy_env. "server.com" will inhibit proxy
+ *        for "server.com", "host.server.com" but not "xserver.com".
+ * @param type the socket type, such as SOCK_STREAM or SOCK_DGRAM.
+ * @param protocol the socket protocol, such as IPPROTO_TCP.
+ * @param close_on_exec if EINA_TRUE, will set SOCK_CLOEXEC.
+ * @param cb the callback to report connection
+ * @param data data to give to callback
+ *
+ * @return an Ecore_Thread that will execute the connection.
+ *
+ * @internal
+ */
+Ecore_Thread *efl_net_ip_connect_async_new(const char *address, const char *proxy, const char *proxy_env, const char *no_proxy_env, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data);
+
 static inline Eina_Error
 efl_net_socket_error_get(void)
 {
index f79d3e3..c2333f3 100644 (file)
@@ -1,6 +1,7 @@
 var @extern Efl.Net.Dialer.Error.COULDNT_CONNECT: Eina.Error;
 var @extern Efl.Net.Dialer.Error.COULDNT_RESOLVE_PROXY: Eina.Error;
 var @extern Efl.Net.Dialer.Error.COULDNT_RESOLVE_HOST: Eina.Error;
+var @extern Efl.Net.Dialer.Error.PROXY_AUTHENTICATION_FAILED: Eina.Error;
 
 interface Efl.Net.Dialer (Efl.Net.Socket) {
     [[Creates a client socket to reach a remote peer.
index a3c2e0b..c114d4d 100644 (file)
@@ -34,11 +34,6 @@ typedef struct _Efl_Net_Dialer_Tcp_Data
 {
    struct {
       Ecore_Thread *thread;
-      struct addrinfo *names;
-      struct addrinfo *current;
-   } resolve;
-   struct {
-      Ecore_Thread *thread;
       Efl_Future *timeout;
    } connect;
    Eina_Stringshare *address_dial;
@@ -74,19 +69,6 @@ _efl_net_dialer_tcp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Tcp_Data *pd)
         pd->connect.thread = NULL;
      }
 
-   if (pd->resolve.thread)
-     {
-        ecore_thread_cancel(pd->resolve.thread);
-        pd->resolve.thread = NULL;
-     }
-
-   pd->resolve.current = NULL;
-   if (pd->resolve.names)
-     {
-        freeaddrinfo(pd->resolve.names);
-        pd->resolve.names = NULL;
-     }
-
    efl_destructor(efl_super(o, MY_CLASS));
 
    eina_stringshare_replace(&pd->address_dial, NULL);
@@ -94,188 +76,96 @@ _efl_net_dialer_tcp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Tcp_Data *pd)
 }
 
 static void
-_efl_net_dialer_tcp_connected(void *data, const struct sockaddr *addr EINA_UNUSED, socklen_t addrlen EINA_UNUSED, int fd, Eina_Error err)
+_efl_net_dialer_tcp_connect_timeout(void *data, const Efl_Event *ev EINA_UNUSED)
 {
    Eo *o = data;
    Efl_Net_Dialer_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS);
-   const struct addrinfo *addrinfo;
-
-   pd->connect.thread = NULL;
-
-   if (!err)
-     {
-        freeaddrinfo(pd->resolve.names);
-        pd->resolve.names = NULL;
-        pd->resolve.current = NULL;
-        efl_loop_fd_set(o, fd);
-        efl_net_dialer_connected_set(o, EINA_TRUE);
-        return;
-     }
-
-   if (!pd->resolve.current) return;
-
-   pd->resolve.current = pd->resolve.current->ai_next;
-   if (!pd->resolve.current)
-     {
-        freeaddrinfo(pd->resolve.names);
-        pd->resolve.names = NULL;
-        efl_io_reader_eos_set(o, EINA_TRUE);
-        if (err)
-          efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err);
-        else
-          {
-             err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
-             efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err);
-          }
-        return;
-     }
-
-   addrinfo = pd->resolve.current;
-   pd->connect.thread = efl_net_connect_async_new(addrinfo->ai_addr,
-                                                  addrinfo->ai_addrlen,
-                                                  SOCK_STREAM,
-                                                  IPPROTO_TCP,
-                                                  efl_io_closer_close_on_exec_get(o),
-                                                  _efl_net_dialer_tcp_connected,
-                                                  o);
-}
-
-static void
-_efl_net_dialer_tcp_connect(Eo *o, Efl_Net_Dialer_Tcp_Data *pd)
-{
-   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
-   const struct addrinfo *addrinfo;
-
-   if (pd->closed || !pd->resolve.current) return;
-
-   efl_ref(o); /* we're emitting callbacks then continuing the workflow */
+   Eina_Error err = ETIMEDOUT;
 
-   addrinfo = pd->resolve.current;
+   pd->connect.timeout = NULL;
 
-   efl_net_socket_fd_family_set(o, addrinfo->ai_family);
-   if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
+   if (pd->connect.thread)
      {
-        efl_net_socket_address_remote_set(o, buf);
-        efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL);
+        ecore_thread_cancel(pd->connect.thread);
+        pd->connect.thread = NULL;
      }
 
-   if (pd->closed) goto end; /* maybe closed from resolved event callback */
-
-   if (pd->connect.thread)
-     ecore_thread_cancel(pd->connect.thread);
-
-   pd->connect.thread = efl_net_connect_async_new(addrinfo->ai_addr,
-                                                  addrinfo->ai_addrlen,
-                                                  SOCK_STREAM,
-                                                  IPPROTO_TCP,
-                                                  efl_io_closer_close_on_exec_get(o),
-                                                  _efl_net_dialer_tcp_connected,
-                                                  o);
- end:
+   efl_ref(o);
+   efl_io_reader_eos_set(o, EINA_TRUE);
+   efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err);
    efl_unref(o);
 }
 
 static void
-_efl_net_dialer_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)
+_efl_net_dialer_tcp_connected(void *data, const struct sockaddr *addr, socklen_t addrlen EINA_UNUSED, int sockfd, Eina_Error err)
 {
    Eo *o = data;
    Efl_Net_Dialer_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS);
+   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+
+   pd->connect.thread = NULL;
 
-   pd->resolve.thread = NULL;
+   efl_ref(o); /* we're emitting callbacks then continuing the workflow */
 
-   if (gai_error)
+   if (err)
      {
-        Eina_Error err = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
         efl_io_reader_eos_set(o, EINA_TRUE);
         efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err);
-        if (result) freeaddrinfo(result);
-        return;
      }
-
-   if (pd->resolve.names) freeaddrinfo(pd->resolve.names);
-   pd->resolve.names = result;
-   pd->resolve.current = result;
-
-   _efl_net_dialer_tcp_connect(o, pd);
-}
-
-static void
-_efl_net_dialer_tcp_connect_timeout(void *data, const Efl_Event *ev EINA_UNUSED)
-{
-   Eo *o = data;
-   Efl_Net_Dialer_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS);
-   Eina_Error err = ETIMEDOUT;
-
-   if (pd->resolve.thread)
+   else
      {
-        ecore_thread_cancel(pd->resolve.thread);
-        pd->resolve.thread = NULL;
-     }
-   if (pd->resolve.names)
-     {
-        freeaddrinfo(pd->resolve.names);
-        pd->resolve.names = NULL;
-     }
-   pd->resolve.current = NULL;
-
-   if (pd->connect.thread)
-     {
-        ecore_thread_cancel(pd->connect.thread);
-        pd->connect.thread = NULL;
+        efl_net_socket_fd_family_set(o, addr->sa_family);
+        efl_loop_fd_set(o, sockfd);
+        if (efl_net_ip_port_fmt(buf, sizeof(buf), addr))
+          {
+             efl_net_socket_address_remote_set(o, buf);
+             efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL);
+          }
+        efl_net_dialer_connected_set(o, EINA_TRUE);
      }
 
-   efl_ref(o);
-   efl_io_reader_eos_set(o, EINA_TRUE);
-   efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err);
    efl_unref(o);
 }
 
 EOLIAN static Eina_Error
 _efl_net_dialer_tcp_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Tcp_Data *pd EINA_UNUSED, const char *address)
 {
-   const char *host, *port;
-   struct addrinfo hints = {
-     .ai_socktype = SOCK_STREAM,
-     .ai_protocol = IPPROTO_TCP,
-   };
-   char *str;
+   const char *proxy_env = NULL, *no_proxy_env = NULL;
 
    EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
    EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_net_dialer_connected_get(o), EISCONN);
    EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF);
    EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) >= 0, EALREADY);
 
-   str = strdup(address);
-   EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
-
-   if (!efl_net_ip_port_split(str, &host, &port))
-     {
-        ERR("could not parse IP:PORT '%s'", address);
-        free(str);
-        return EINVAL;
-     }
-   if (strchr(host, ':')) hints.ai_family = AF_INET6;
-   if (!port) port = "0";
-
    if (pd->connect.thread)
      {
         ecore_thread_cancel(pd->connect.thread);
         pd->connect.thread = NULL;
      }
 
-   if (pd->resolve.names)
+   if (pd->connect.thread)
+     ecore_thread_cancel(pd->connect.thread);
+
+   if (!pd->proxy)
      {
-        freeaddrinfo(pd->resolve.names);
-        pd->resolve.names = NULL;
-     }
-   pd->resolve.current = NULL;
+        proxy_env = getenv("socks_proxy");
+        if (!proxy_env) proxy_env = getenv("SOCKS_PROXY");
+        if (!proxy_env) proxy_env = getenv("all_proxy");
+        if (!proxy_env) proxy_env = getenv("ALL_PROXY");
 
-   if (pd->resolve.thread)
-     ecore_thread_cancel(pd->resolve.thread);
+        no_proxy_env = getenv("no_proxy");
+        if (!no_proxy_env) no_proxy_env = getenv("NO_PROXY");
+     }
 
-   pd->resolve.thread = efl_net_resolve_async_new(host, port, &hints, _efl_net_dialer_tcp_resolved, o);
-   free(str);
-   EINA_SAFETY_ON_NULL_RETURN_VAL(pd->resolve.thread, EINVAL);
+   pd->connect.thread = efl_net_ip_connect_async_new(address,
+                                                     pd->proxy,
+                                                     proxy_env,
+                                                     no_proxy_env,
+                                                     SOCK_STREAM,
+                                                     IPPROTO_TCP,
+                                                     efl_io_closer_close_on_exec_get(o),
+                                                     _efl_net_dialer_tcp_connected, o);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(pd->connect.thread, EINVAL);
 
    efl_net_dialer_address_dial_set(o, address);
 
@@ -305,7 +195,6 @@ _efl_net_dialer_tcp_efl_net_dialer_address_dial_get(Eo *o EINA_UNUSED, Efl_Net_D
 EOLIAN static void
 _efl_net_dialer_tcp_efl_net_dialer_proxy_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Tcp_Data *pd, const char *proxy_url)
 {
-   // TODO: apply proxy
    eina_stringshare_replace(&pd->proxy, proxy_url);
 }
 
@@ -321,7 +210,8 @@ _efl_net_dialer_tcp_efl_net_dialer_timeout_dial_set(Eo *o EINA_UNUSED, Efl_Net_D
    pd->timeout_dial = seconds;
    if (pd->connect.timeout)
      efl_future_cancel(pd->connect.timeout);
-   if (pd->timeout_dial > 0.0)
+
+   if ((pd->timeout_dial > 0.0) && (pd->connect.thread))
      {
         efl_future_use(&pd->connect.timeout, efl_loop_timeout(efl_loop_user_loop_get(o), pd->timeout_dial, o));
         efl_future_then(pd->connect.timeout, _efl_net_dialer_tcp_connect_timeout, NULL, NULL, o);