From: Gustavo Sverzut Barbieri Date: Mon, 19 Sep 2016 03:56:18 +0000 (-0300) Subject: efl_net_dialer_tcp: add SOCKS proxy support. X-Git-Tag: upstream/1.20.0~4313 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a975bfe1e60da8ef3c38095b537b9aeda33cf0f8;p=platform%2Fupstream%2Fefl.git efl_net_dialer_tcp: add SOCKS proxy support. 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). --- diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h index f10d1d7..ec7a228 100644 --- a/src/lib/ecore_con/Ecore_Con_Eo.h +++ b/src/lib/ecore_con/Ecore_Con_Eo.h @@ -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; diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c index 4efcb2a..7d8c379 100644 --- a/src/lib/ecore_con/ecore_con.c +++ b/src/lib/ecore_con/ecore_con.c @@ -47,6 +47,10 @@ #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; } diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h index 94493b0..6437e83 100644 --- a/src/lib/ecore_con/ecore_con_private.h +++ b/src/lib/ecore_con/ecore_con_private.h @@ -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) { diff --git a/src/lib/ecore_con/efl_net_dialer.eo b/src/lib/ecore_con/efl_net_dialer.eo index f79d3e3..c2333f3 100644 --- a/src/lib/ecore_con/efl_net_dialer.eo +++ b/src/lib/ecore_con/efl_net_dialer.eo @@ -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. diff --git a/src/lib/ecore_con/efl_net_dialer_tcp.c b/src/lib/ecore_con/efl_net_dialer_tcp.c index a3c2e0b..c114d4d 100644 --- a/src/lib/ecore_con/efl_net_dialer_tcp.c +++ b/src/lib/ecore_con/efl_net_dialer_tcp.c @@ -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);