efl/src/lib/ecore_con/ecore_con.c

2596 lines
84 KiB
C
Raw Normal View History

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
2010-01-04 15:43:16 -08:00
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
2013-03-15 01:12:05 -07:00
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_TCP_H
# include <netinet/tcp.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#ifdef HAVE_SYSTEMD
# include <systemd/sd-daemon.h>
#endif
#ifdef _WIN32
# include <ws2tcpip.h>
# include <evil_private.h> /* evil_init|shutdown */
#endif
#include "Ecore.h"
#include "ecore_private.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0 /* noop */
#endif
#ifdef HAVE_SYSTEMD
int sd_fd_index = 0;
int sd_fd_max = 0;
#endif
EWAPI Eina_Error EFL_NET_ERROR_COULDNT_RESOLVE_HOST = 0;
EWAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_CONNECT = 0;
EWAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY = 0;
EWAPI Eina_Error EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = 0;
EWAPI Eina_Error EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE = 0;
EWAPI Eina_Error EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED = 0;
static int _ecore_con_init_count = 0;
int _ecore_con_log_dom = -1;
ecore_con - move libproxy to a slave binary with stdin/out msging so here's the ugly problem. libproxy. yes. we've discussed memory usage (e.g. it may have to execute javascript and pull in lots of deps etc.) but we dlopene'd on the fly. ok... but this didn't solve another issue i hit: libproxy was causing enlightenment to abort(). some internal bit of libproxy was raising a c++ exception. this wasn't caught. this causes an abort(). takes down your entire desktop. FANTASTIC. this is bad. i wouldnt' expect a library we depend on to be THIS anti-social but libproxy seemingly is. it SHOULd catch its error sand just propagate back to us so we can handle gracefully. there reall is no way around this - isolate libproxy. it's even worse that libproxy can load arbitrary modules that come from anywhere sho who knows what issues this can cause. isolation is the best solution i can think of. so this makes an elf+net_proxy_helper we spawn the first time we need a proxy lookup. we re-use that binary again and again until it exits (it should exit after 10 seconds of being idle with no requests coming in/pending). it'll respawn again later if needed. this involves now the efl net threads having to marshall back to mainloop to do the spawn and to write to the proxy process (reading is done by async exe data events and the data is passed down a thread queue to the waitng efl net thread). if the exe dies with pending requests unanswered then it's respawned again and the req's are re-sent to it... just in case. it has a limit on how often it'll respawn quickly. this seems to work in my limited testing. this ALSO now isolates memory usage of libproxy to another slave process AND this process will die taking its memory with it once it's been idle for long enough. that;s also another good solution to keeping libproxy impact at bay.
2017-01-08 05:57:54 -08:00
Eina_Bool _efl_net_proxy_helper_can_do (void);
int _efl_net_proxy_helper_url_req_send(const char *url, Ecore_Thread *eth);
ecore_con - move libproxy to a slave binary with stdin/out msging so here's the ugly problem. libproxy. yes. we've discussed memory usage (e.g. it may have to execute javascript and pull in lots of deps etc.) but we dlopene'd on the fly. ok... but this didn't solve another issue i hit: libproxy was causing enlightenment to abort(). some internal bit of libproxy was raising a c++ exception. this wasn't caught. this causes an abort(). takes down your entire desktop. FANTASTIC. this is bad. i wouldnt' expect a library we depend on to be THIS anti-social but libproxy seemingly is. it SHOULd catch its error sand just propagate back to us so we can handle gracefully. there reall is no way around this - isolate libproxy. it's even worse that libproxy can load arbitrary modules that come from anywhere sho who knows what issues this can cause. isolation is the best solution i can think of. so this makes an elf+net_proxy_helper we spawn the first time we need a proxy lookup. we re-use that binary again and again until it exits (it should exit after 10 seconds of being idle with no requests coming in/pending). it'll respawn again later if needed. this involves now the efl net threads having to marshall back to mainloop to do the spawn and to write to the proxy process (reading is done by async exe data events and the data is passed down a thread queue to the waitng efl net thread). if the exe dies with pending requests unanswered then it's respawned again and the req's are re-sent to it... just in case. it has a limit on how often it'll respawn quickly. this seems to work in my limited testing. this ALSO now isolates memory usage of libproxy to another slave process AND this process will die taking its memory with it once it's been idle for long enough. that;s also another good solution to keeping libproxy impact at bay.
2017-01-08 05:57:54 -08:00
char **_efl_net_proxy_helper_url_wait (int id);
void _efl_net_proxy_helper_init (void);
void _efl_net_proxy_helper_shutdown (void);
EAPI int
ecore_con_init(void)
{
if (++_ecore_con_init_count != 1)
return _ecore_con_init_count;
#ifdef _WIN32
if (!evil_init())
return --_ecore_con_init_count;
#endif
if (!eina_init())
goto eina_err;
_ecore_con_log_dom = eina_log_domain_register
("ecore_con", ECORE_CON_DEFAULT_LOG_COLOR);
2010-11-05 18:22:50 -07:00
if (_ecore_con_log_dom < 0)
goto ecore_con_log_error;
if (!ecore_init())
goto ecore_err;
ecore_con - move libproxy to a slave binary with stdin/out msging so here's the ugly problem. libproxy. yes. we've discussed memory usage (e.g. it may have to execute javascript and pull in lots of deps etc.) but we dlopene'd on the fly. ok... but this didn't solve another issue i hit: libproxy was causing enlightenment to abort(). some internal bit of libproxy was raising a c++ exception. this wasn't caught. this causes an abort(). takes down your entire desktop. FANTASTIC. this is bad. i wouldnt' expect a library we depend on to be THIS anti-social but libproxy seemingly is. it SHOULd catch its error sand just propagate back to us so we can handle gracefully. there reall is no way around this - isolate libproxy. it's even worse that libproxy can load arbitrary modules that come from anywhere sho who knows what issues this can cause. isolation is the best solution i can think of. so this makes an elf+net_proxy_helper we spawn the first time we need a proxy lookup. we re-use that binary again and again until it exits (it should exit after 10 seconds of being idle with no requests coming in/pending). it'll respawn again later if needed. this involves now the efl net threads having to marshall back to mainloop to do the spawn and to write to the proxy process (reading is done by async exe data events and the data is passed down a thread queue to the waitng efl net thread). if the exe dies with pending requests unanswered then it's respawned again and the req's are re-sent to it... just in case. it has a limit on how often it'll respawn quickly. this seems to work in my limited testing. this ALSO now isolates memory usage of libproxy to another slave process AND this process will die taking its memory with it once it's been idle for long enough. that;s also another good solution to keeping libproxy impact at bay.
2017-01-08 05:57:54 -08:00
_efl_net_proxy_helper_init();
ecore_con_mempool_init();
ecore_con_legacy_init();
EFL_NET_ERROR_COULDNT_RESOLVE_HOST = eina_error_msg_static_register("Couldn't resolve host name");
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_PROXY_AUTHENTICATION_FAILED = eina_error_msg_static_register("Proxy authentication failed");
EFL_NET_SOCKET_SSL_ERROR_HANDSHAKE = eina_error_msg_static_register("Failed SSL handshake");
EFL_NET_SOCKET_SSL_ERROR_CERTIFICATE_VERIFY_FAILED = eina_error_msg_static_register("Failed to verify peer's certificate");
#ifdef HAVE_SYSTEMD
sd_fd_max = sd_listen_fds(0);
#endif
2013-03-27 05:42:55 -07:00
eina_log_timing(_ecore_con_log_dom,
2013-12-19 21:07:50 -08:00
EINA_LOG_STATE_STOP,
EINA_LOG_STATE_INIT);
2013-03-27 05:42:55 -07:00
return _ecore_con_init_count;
ecore_con_log_error:
EINA_LOG_ERR("Failed to create a log domain for Ecore Con.");
ecore_shutdown();
ecore_err:
eina_shutdown();
eina_err:
#ifdef _WIN32
evil_shutdown();
#endif
return --_ecore_con_init_count;
}
EAPI int
ecore_con_shutdown(void)
{
/* _ecore_con_init_count should not go below zero. */
if (_ecore_con_init_count < 1)
{
ERR("Ecore_Con Shutdown called without calling Ecore_Con Init.\n");
return 0;
}
if (--_ecore_con_init_count != 0)
return _ecore_con_init_count;
ecore_con - move libproxy to a slave binary with stdin/out msging so here's the ugly problem. libproxy. yes. we've discussed memory usage (e.g. it may have to execute javascript and pull in lots of deps etc.) but we dlopene'd on the fly. ok... but this didn't solve another issue i hit: libproxy was causing enlightenment to abort(). some internal bit of libproxy was raising a c++ exception. this wasn't caught. this causes an abort(). takes down your entire desktop. FANTASTIC. this is bad. i wouldnt' expect a library we depend on to be THIS anti-social but libproxy seemingly is. it SHOULd catch its error sand just propagate back to us so we can handle gracefully. there reall is no way around this - isolate libproxy. it's even worse that libproxy can load arbitrary modules that come from anywhere sho who knows what issues this can cause. isolation is the best solution i can think of. so this makes an elf+net_proxy_helper we spawn the first time we need a proxy lookup. we re-use that binary again and again until it exits (it should exit after 10 seconds of being idle with no requests coming in/pending). it'll respawn again later if needed. this involves now the efl net threads having to marshall back to mainloop to do the spawn and to write to the proxy process (reading is done by async exe data events and the data is passed down a thread queue to the waitng efl net thread). if the exe dies with pending requests unanswered then it's respawned again and the req's are re-sent to it... just in case. it has a limit on how often it'll respawn quickly. this seems to work in my limited testing. this ALSO now isolates memory usage of libproxy to another slave process AND this process will die taking its memory with it once it's been idle for long enough. that;s also another good solution to keeping libproxy impact at bay.
2017-01-08 05:57:54 -08:00
_efl_net_proxy_helper_shutdown();
2013-03-27 05:42:55 -07:00
eina_log_timing(_ecore_con_log_dom,
2013-12-19 21:07:50 -08:00
EINA_LOG_STATE_START,
EINA_LOG_STATE_SHUTDOWN);
2013-03-27 05:42:55 -07:00
ecore_con_legacy_shutdown();
ecore_shutdown();
#ifdef _WIN32
evil_shutdown();
#endif
eina_log_domain_unregister(_ecore_con_log_dom);
_ecore_con_log_dom = -1;
eina_shutdown();
return _ecore_con_init_count;
}
EAPI int
ecore_con_ssl_available_get(void)
{
#if HAVE_GNUTLS
return 1;
#elif HAVE_OPENSSL
return 2;
#else
return 0;
#endif
}
#ifndef _WIN32
Eina_Bool
efl_net_unix_fmt(char *buf, size_t buflen, SOCKET fd, const struct sockaddr_un *addr, socklen_t addrlen)
{
const char *src = addr->sun_path;
socklen_t pathlen = addrlen - offsetof(struct sockaddr_un, sun_path);
if (addr->sun_family != AF_UNIX)
{
ERR("unsupported address family: %d", addr->sun_family);
return EINA_FALSE;
}
if (addrlen == offsetof(struct sockaddr_un, sun_path))
{
int r = snprintf(buf, buflen, "unnamed:" SOCKET_FMT, fd);
if (r < 0)
{
ERR("snprintf(): %s", eina_error_msg_get(errno));
return EINA_FALSE;
}
else if ((size_t)r > buflen)
{
ERR("buflen=%zu is too small, required=%d", buflen, r);
return EINA_FALSE;
}
return EINA_TRUE;
}
if (src[0] != '\0')
{
if (buflen < pathlen)
{
ERR("buflen=%zu is too small, required=%u", buflen, pathlen);
return EINA_FALSE;
}
}
else
{
if (buflen < pathlen + sizeof("abstract:") - 2)
{
ERR("buflen=%zu is too small, required=%zu", buflen, pathlen + sizeof("abstract:") - 2);
return EINA_FALSE;
}
memcpy(buf, "abstract:", sizeof("abstract:") - 1);
buf += sizeof("abstract:") - 1;
src++;
}
memcpy(buf, src, pathlen);
buf[pathlen] = '\0';
return EINA_TRUE;
}
#endif
/* The reverse of efl_net_ip_port_fmt().
*
* If was parsed, then returns EINA_TRUE, otherwise use getaddrinfo()
* or efl_net_ip_resolve_async_new().
*/
Eina_Bool
efl_net_ip_port_parse(const char *address, struct sockaddr_storage *storage)
{
char *str;
const char *host, *port;
Eina_Bool ret;
str = strdup(address);
EINA_SAFETY_ON_NULL_RETURN_VAL(str, EINA_FALSE);
if (!efl_net_ip_port_split(str, &host, &port))
{
ERR("invalid IP:PORT address: %s", address);
ret = EINA_FALSE;
}
else
ret = efl_net_ip_port_parse_split(host, port, storage);
free(str);
return ret;
}
Eina_Bool
efl_net_ip_port_parse_split(const char *host, const char *port, struct sockaddr_storage *storage)
{
int x;
char *endptr;
unsigned long p;
if (!port) port = "0";
if (strchr(host, ':')) storage->ss_family = AF_INET6;
else storage->ss_family = AF_INET;
errno = 0;
p = strtoul(port, &endptr, 10);
if ((errno) || (endptr == port) || (*endptr != '\0')) return EINA_FALSE;
else if (p > UINT16_MAX)
{
ERR("invalid port number %lu (out of range)", p);
return EINA_FALSE;
}
if (storage->ss_family == AF_INET6)
{
struct sockaddr_in6 *a = (struct sockaddr_in6 *)storage;
a->sin6_port = eina_htons(p);
x = inet_pton(AF_INET6, host, &a->sin6_addr);
}
else
{
struct sockaddr_in *a = (struct sockaddr_in *)storage;
a->sin_port = eina_htons(p);
x = inet_pton(AF_INET, host, &a->sin_addr);
}
return x == 1;
}
Eina_Bool
efl_net_ip_port_fmt(char *buf, size_t buflen, const struct sockaddr *addr)
{
char p[INET6_ADDRSTRLEN];
const void *mem;
unsigned short port;
int r;
if (addr->sa_family == AF_INET)
{
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
mem = &a->sin_addr;
port = eina_ntohs(a->sin_port);
}
else if (addr->sa_family == AF_INET6)
{
const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
mem = &a->sin6_addr;
port = eina_ntohs(a->sin6_port);
}
else
{
ERR("unsupported address family: %d", addr->sa_family);
return EINA_FALSE;
}
if (!inet_ntop(addr->sa_family, mem, p, sizeof(p)))
{
ERR("inet_ntop(%d, %p, %p, %zd): %s",
addr->sa_family, mem, p, sizeof(p), eina_error_msg_get(errno));
return EINA_FALSE;
}
if (addr->sa_family == AF_INET)
r = snprintf(buf, buflen, "%s:%hu", p, port);
else
r = snprintf(buf, buflen, "[%s]:%hu", p, port);
if (r < 0)
{
ERR("could not snprintf(): %s", eina_error_msg_get(errno));
return EINA_FALSE;
}
else if ((size_t)r > buflen)
{
ERR("buffer is too small: %zu, required %d", buflen, r);
return EINA_FALSE;
}
return EINA_TRUE;
}
Eina_Bool
efl_net_ip_port_split(char *buf, const char **p_host, const char **p_port)
{
char *host, *port;
EINA_SAFETY_ON_NULL_RETURN_VAL(buf, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(p_host, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(p_port, EINA_FALSE);
host = buf;
if (host[0] == '[')
{
/* IPv6 is: [IP]:port */
host++;
port = strchr(host, ']');
if (!port) return EINA_FALSE;
*port = '\0';
port++;
if (port[0] == ':')
port++;
else
port = NULL;
}
else
{
port = strchr(host, ':');
if (port)
{
*port = '\0';
port++;
if (*port == '\0') port = NULL;
}
}
*p_host = host;
*p_port = port;
return EINA_TRUE;
}
#ifdef HAVE_SYSTEMD
Eina_Error
efl_net_ip_socket_activate_check(const char *address, int family, int type, Eina_Bool *listening)
{
SOCKET fd = SD_LISTEN_FDS_START + sd_fd_index;
int r;
if (sd_fd_index >= sd_fd_max) return ENOENT;
if (family == AF_UNIX)
{
char buf[sizeof(struct sockaddr_un)] = "";
const char *sun_path;
size_t len;
if (strncmp(address, "abstract:", strlen("abstract:")) == 0)
{
const char *path = address + strlen("abstract:");
if (strlen(path) + 2 > sizeof(buf))
{
ERR("abstract path is too long: %s", path);
return EINVAL;
}
buf[0] = '\0';
memcpy(buf + 1, path, strlen(path) + 1);
sun_path = buf;
len = strlen(path) + 2;
}
else
{
if (strlen(address) + 1 > sizeof(buf))
{
ERR("path is too long: %s", address);
return EINVAL;
}
sun_path = address;
len = strlen(address) + 1;
}
r = sd_is_socket_unix(fd, type, 0, sun_path, len);
if (r < 0)
{
ERR("socket " SOCKET_FMT " is not of family=%d, type=%d", fd, family, type);
return EINVAL;
}
if (listening) *listening = (r == 1);
return 0;
}
else if ((family == AF_UNSPEC) || (family == AF_INET) || (family == AF_INET6))
{
char *str;
const char *host, *port;
struct sockaddr_storage sock_addr;
struct sockaddr_storage want_addr = { .ss_family = family };
socklen_t addrlen;
Eina_Error err;
int x;
r = sd_is_socket(fd, family, type, (type == SOCK_DGRAM) ? -1 : 0);
if (r < 0)
{
ERR("socket " SOCKET_FMT " is not of family=%d, type=%d", fd, family, type);
return EINVAL;
}
if ((type == SOCK_DGRAM) && (listening)) *listening = EINA_FALSE;
else if (listening) *listening = (r == 1);
addrlen = sizeof(sock_addr);
if (getsockname(fd, (struct sockaddr *)&sock_addr, &addrlen) != 0)
{
err = efl_net_socket_error_get();
ERR("could not query socket=" SOCKET_FMT " name: %s", fd, eina_error_msg_get(err));
return err;
}
str = strdup(address);
EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
if (!efl_net_ip_port_split(str, &host, &port))
{
ERR("invalid IP:PORT address: %s", address);
free(str);
return EINVAL;
}
if (!port) port = "0";
if (efl_net_ip_port_parse_split(host, port, &want_addr))
{
Eina_Bool matches;
if (want_addr.ss_family != sock_addr.ss_family)
{
ERR("socket " SOCKET_FMT " family=%d differs from wanted %d", fd, sock_addr.ss_family, want_addr.ss_family);
free(str);
return EINVAL;
}
else if (want_addr.ss_family == AF_INET6)
matches = memcmp(&want_addr, &sock_addr, sizeof(struct sockaddr_in6)) == 0;
else
matches = memcmp(&want_addr, &sock_addr, sizeof(struct sockaddr_in)) == 0;
if (!matches)
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&sock_addr);
ERR("socket " SOCKET_FMT " address %s differs from wanted %s", fd, buf, address);
free(str);
return EINVAL;
}
free(str);
return 0;
}
else
{
/*
* NOTE: this may block, but users should be using the IP:PORT
* as numbers, getting into the fast path above.
*
* This is best-try to help API to be usable, but may
* impact the main loop execution for a while. However
* people doing bind are expected to do so on a local
* address, usually resolves faster without too many DNS
* lookups.
*/
struct addrinfo hints = {
.ai_socktype = type,
.ai_family = family,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
struct addrinfo *results, *itr;
DBG("resolving '%s', it may block main loop! Consider using IP:PORT", address);
do
{
x = getaddrinfo(host, port, &hints, &results);
}
while ((x == EAI_AGAIN) || ((x == EAI_SYSTEM) && (errno == EINTR)));
if (x != 0)
{
ERR("couldn't resolve host='%s', port='%s': %s",
host, port, gai_strerror(x));
free(str);
return EINVAL;
}
err = EINVAL;
for (itr = results; itr != NULL; itr = itr->ai_next)
{
if (sock_addr.ss_family != itr->ai_family) continue;
if (memcmp(itr->ai_addr, &sock_addr, itr->ai_addrlen) == 0)
{
err = 0;
break;
}
}
freeaddrinfo(results);
free(str);
return err;
}
}
else
{
if (listening) *listening = EINA_FALSE;
ERR("unsupported family=%d", family);
return EINVAL;
}
}
#endif
static void
_cleanup_close(void *data)
{
SOCKET *p_fd = data;
SOCKET fd = *p_fd;
*p_fd = INVALID_SOCKET;
if (fd != INVALID_SOCKET) closesocket(fd);
}
SOCKET
efl_net_socket4(int domain, int type, int protocol, Eina_Bool close_on_exec)
{
SOCKET fd = INVALID_SOCKET;
#ifdef SOCK_CLOEXEC
if (close_on_exec) type |= SOCK_CLOEXEC;
#endif
fd = socket(domain, type, protocol);
#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC)
EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
if (fd != INVALID_SOCKET)
{
if (close_on_exec)
{
if (!eina_file_close_on_exec(fd, EINA_TRUE))
{
int errno_bkp = errno;
closesocket(fd);
fd = INVALID_SOCKET;
errno = errno_bkp;
}
}
}
EINA_THREAD_CLEANUP_POP(EINA_FALSE); /* we need fd on success */
#else
DBG("close on exec is not supported on your platform");
(void)close_on_exec;
#endif
return fd;
}
typedef struct _Efl_Net_Ip_Resolve_Async_Data
{
Efl_Net_Ip_Resolve_Async_Cb cb;
const void *data;
char *host;
char *port;
struct addrinfo *result;
struct addrinfo *hints;
int gai_error;
} Efl_Net_Ip_Resolve_Async_Data;
static void
_efl_net_ip_resolve_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Ip_Resolve_Async_Data *d = data;
/* allows ecore_thread_cancel() to cancel at some points, see
* man:pthreads(7).
*
* no need to set cleanup functions since the main thread will
* handle that with _efl_net_ip_resolve_async_cancel().
*/
eina_thread_cancellable_set(EINA_TRUE, NULL);
while (EINA_TRUE)
{
DBG("resolving host='%s' port='%s'", d->host, d->port);
d->gai_error = getaddrinfo(d->host, d->port, d->hints, &d->result);
if (d->gai_error == 0) break;
if (d->gai_error == EAI_AGAIN) continue;
if ((d->gai_error == EAI_SYSTEM) && (errno == EINTR)) continue;
DBG("getaddrinfo(\"%s\", \"%s\") failed: %s", d->host, d->port, gai_strerror(d->gai_error));
break;
}
eina_thread_cancellable_set(EINA_FALSE, NULL);
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
const struct addrinfo *addrinfo;
for (addrinfo = d->result; addrinfo != NULL; addrinfo = addrinfo->ai_next)
{
if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
DBG("resolved host='%s' port='%s': %s", d->host, d->port, buf);
}
}
}
static void
_efl_net_ip_resolve_async_data_free(Efl_Net_Ip_Resolve_Async_Data *d)
{
free(d->hints);
free(d->host);
free(d->port);
free(d);
}
static void
_efl_net_ip_resolve_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Ip_Resolve_Async_Data *d = data;
d->cb((void *)d->data, d->host, d->port, d->hints, d->result, d->gai_error);
_efl_net_ip_resolve_async_data_free(d);
}
static void
_efl_net_ip_resolve_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Ip_Resolve_Async_Data *d = data;
if (d->result) freeaddrinfo(d->result);
_efl_net_ip_resolve_async_data_free(d);
}
Ecore_Thread *
efl_net_ip_resolve_async_new(const char *host, const char *port, const struct addrinfo *hints, Efl_Net_Ip_Resolve_Async_Cb cb, const void *data)
{
Efl_Net_Ip_Resolve_Async_Data *d;
EINA_SAFETY_ON_NULL_RETURN_VAL(host, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(port, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
d = malloc(sizeof(Efl_Net_Ip_Resolve_Async_Data));
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
d->cb = cb;
d->data = data;
d->host = strdup(host);
EINA_SAFETY_ON_NULL_GOTO(d->host, failed_host);
d->port = strdup(port);
EINA_SAFETY_ON_NULL_GOTO(d->port, failed_port);
if (!hints) d->hints = NULL;
else
{
d->hints = malloc(sizeof(struct addrinfo));
EINA_SAFETY_ON_NULL_GOTO(d->hints, failed_hints);
memcpy(d->hints, hints, sizeof(struct addrinfo));
}
d->result = NULL;
return ecore_thread_feedback_run(_efl_net_ip_resolve_async_run,
NULL,
_efl_net_ip_resolve_async_end,
_efl_net_ip_resolve_async_cancel,
d, EINA_TRUE);
failed_hints:
free(d->port);
failed_port:
free(d->host);
failed_host:
free(d);
return NULL;
}
typedef struct _Efl_Net_Connect_Async_Data
{
Efl_Net_Connect_Async_Cb cb;
const void *data;
socklen_t addrlen;
Eina_Bool close_on_exec;
int type;
int protocol;
SOCKET sockfd;
Eina_Error error;
struct sockaddr addr[];
} Efl_Net_Connect_Async_Data;
static void
_efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Connect_Async_Data *d = data;
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")
#ifndef _WIN32
+ sizeof(struct sockaddr_un)
#endif
] = "";
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);
d->error = 0;
/* 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 == INVALID_SOCKET)
{
d->error = efl_net_socket_error_get();
DBG("socket(%d, %d, %d) failed: %s", d->addr->sa_family, d->type, d->protocol, eina_error_msg_get(d->error));
return;
}
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
{
#ifndef _WIN32
if (d->addr->sa_family == AF_UNIX)
efl_net_unix_fmt(buf, sizeof(buf), d->sockfd, (const struct sockaddr_un *)d->addr, d->addrlen);
else
#endif
efl_net_ip_port_fmt(buf, sizeof(buf), d->addr);
}
DBG("connecting fd=" SOCKET_FMT " to %s", d->sockfd, buf);
r = connect(d->sockfd, d->addr, d->addrlen);
if (r != 0)
{
SOCKET fd = d->sockfd;
d->error = efl_net_socket_error_get();
d->sockfd = INVALID_SOCKET;
/* close() is a cancellation point, thus unset sockfd before
* closing, so the main thread _efl_net_connect_async_cancel()
* won't close it again.
*/
closesocket(fd);
DBG("connect(" SOCKET_FMT ", %s) failed: %s", fd, buf, eina_error_msg_get(d->error));
return;
}
DBG("connected fd=" SOCKET_FMT " to %s", d->sockfd, buf);
}
static void
_efl_net_connect_async_data_free(Efl_Net_Connect_Async_Data *d)
{
free(d);
}
static void
_efl_net_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
{
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 != INVALID_SOCKET))
{
if (!eina_file_close_on_exec(d->sockfd, EINA_FALSE))
{
d->error = errno;
closesocket(d->sockfd);
d->sockfd = INVALID_SOCKET;
}
}
#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_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Connect_Async_Data *d = data;
if (d->sockfd != INVALID_SOCKET) closesocket(d->sockfd);
_efl_net_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_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(cb, NULL);
d = malloc(sizeof(Efl_Net_Connect_Async_Data) + addrlen);
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
d->cb = cb;
d->data = data;
d->addrlen = addrlen;
d->close_on_exec = close_on_exec;
d->type = type;
d->protocol = protocol;
memcpy(d->addr, addr, addrlen);
d->sockfd = INVALID_SOCKET;
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++)
{
const char *s = *itr;
size_t slen;
/* '*' is not a glob/pattern, it matches all */
if (*s == '*') return EINA_TRUE;
/* old timers use leading dot to avoid matching partial names
* due implementation bugs not required anymore
*/
if (*s == '.') s++;
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_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;
SOCKET sockfd;
Eina_Error error;
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, SOCKET *sockfd)
{
SOCKET fd = INVALID_SOCKET;
volatile 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 == INVALID_SOCKET) ret = efl_net_socket_error_get();
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=" SOCKET_FMT " to %s", fd, buf);
}
r = connect(fd, addr->ai_addr, addr->ai_addrlen);
if (r == 0)
{
DBG("connected fd=" SOCKET_FMT " to %s", fd, buf);
*sockfd = fd;
}
else
{
ret = efl_net_socket_error_get();
DBG("couldn't connect fd=" SOCKET_FMT " to %s: %s", fd, buf, eina_error_msg_get(ret));
closesocket(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, SOCKET *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,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
volatile 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_ERROR_COULDNT_RESOLVE_HOST;
*sockfd = INVALID_SOCKET;
}
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)
{
if (results)
{
memcpy(addr, results->ai_addr, results->ai_addrlen);
*p_addrlen = results->ai_addrlen;
}
ret = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
}
EINA_THREAD_CLEANUP_POP(EINA_TRUE);
}
return ret;
}
static void
_efl_net_ip_connect_async_run_direct(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port)
{
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;
SOCKET fd;
Eina_Error err;
volatile 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=" SOCKET_FMT " 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, (const char *)request, request_len, MSG_NOSIGNAL);
if (s != (ssize_t)request_len)
{
if (s == SOCKET_ERROR)
DBG("couldn't request connection to host=%s fd=" SOCKET_FMT " socks4://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
else
DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
}
else
{
Efl_Net_Socks4_Reply reply;
s = recv(fd, (char *)&reply, sizeof(reply), MSG_NOSIGNAL);
if (s != sizeof(reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv reply of connection to host=%s fd=" SOCKET_FMT " socks4://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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=" SOCKET_FMT " 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,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
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 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))
{
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_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)
{
SOCKET fd = INVALID_SOCKET;
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,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
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=" SOCKET_FMT " 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_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, (const char *)request, request_len, MSG_NOSIGNAL);
if (s != (ssize_t)request_len)
{
if (s == SOCKET_ERROR)
DBG("couldn't send proxy request: %s", eina_error_msg_get(efl_net_socket_error_get()));
else
DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
}
else
{
Efl_Net_Socks4_Reply reply;
s = recv(fd, (char *)&reply, sizeof(reply), MSG_NOSIGNAL);
if (s != sizeof(reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv proxy reply: %s", eina_error_msg_get(efl_net_socket_error_get()));
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 == INVALID_SOCKET); /* 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(SOCKET fd, const char *user, const char *pass, const char *proxy_protocol, const char *proxy_host, const char *proxy_port)
{
volatile uint8_t user_len = user ? strlen(user) : 0;
volatile uint8_t pass_len = pass ? strlen(pass) : 0;
size_t len = 1 + 1 + user_len + 1 + pass_len;
char *msg;
volatile 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 == SOCKET_ERROR)
DBG("couldn't send user-password authentication to fd=" SOCKET_FMT " %s://%s:%s: %s", fd, proxy_protocol, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
else
DBG("couldn't send user-password authentication: need %zu, did %zd", len, s);
}
else
{
uint8_t reply[2];
s = recv(fd, (char *)&reply, sizeof(reply), MSG_NOSIGNAL);
if (s != (ssize_t)sizeof(reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv user-password authentication reply from fd=" SOCKET_FMT " %s://%s:%s: %s", fd, proxy_protocol, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " %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;
SOCKET fd;
Eina_Error err;
volatile 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))
{
if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
DBG("resolved address='%s' to %s. Connect using fd=" SOCKET_FMT " socks5://%s:%s", d->address, buf, fd, proxy_host, proxy_port);
}
s = send(fd, (const char *)&greeting, sizeof(greeting), MSG_NOSIGNAL);
if (s != (ssize_t)sizeof(greeting))
{
if (s == SOCKET_ERROR)
DBG("couldn't request connection to host=%s fd=" SOCKET_FMT " socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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, (char *)&greeting_reply, sizeof(greeting_reply), MSG_NOSIGNAL);
if (s != sizeof(greeting_reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv greeting reply of connection to host=%s fd=" SOCKET_FMT " socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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, (const char *)request, request_len, MSG_NOSIGNAL);
if (s != (ssize_t)request_len)
{
if (s == SOCKET_ERROR)
DBG("couldn't request connection to host=%s fd=" SOCKET_FMT " socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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, (char *)&reply, sizeof(reply), MSG_NOSIGNAL);
if (s != sizeof(reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv reply of connection to host=%s fd=" SOCKET_FMT " socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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=" SOCKET_FMT " 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, (char *)&reply, sizeof(reply), MSG_NOSIGNAL);
if (s != sizeof(reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv reply of connection to host=%s fd=" SOCKET_FMT " socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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=" SOCKET_FMT " 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;
}
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,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
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_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)
{
SOCKET fd = INVALID_SOCKET;
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=" SOCKET_FMT " to socks5h://%s", fd, proxy);
EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
s = send(fd, (const char *)&greeting, sizeof(greeting), MSG_NOSIGNAL);
if (s != (ssize_t)sizeof(greeting))
{
if (s == SOCKET_ERROR)
DBG("couldn't request connection to host=%s:%s fd=" SOCKET_FMT " socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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, (char *)&greeting_reply, sizeof(greeting_reply), MSG_NOSIGNAL);
if (s != sizeof(greeting_reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv greeting reply of connection to host=%s:%s fd=" SOCKET_FMT " socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
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_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, (const char *)request, request_len, MSG_NOSIGNAL);
if (s != (ssize_t)request_len)
{
if (s == SOCKET_ERROR)
DBG("couldn't request connection to host=%s:%s fd=" SOCKET_FMT " socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
else
DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
}
else
{
Efl_Net_Socks5_Reply reply;
s = recv(fd, (char *)&reply, sizeof(reply), MSG_NOSIGNAL);
if (s != sizeof(reply))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv reply of connection to host=%s:%s fd=" SOCKET_FMT " socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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, (char *)&ipv4, sizeof(ipv4), MSG_NOSIGNAL);
if (s != sizeof(ipv4))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv ipv4 of connection to host=%s:%s fd=" SOCKET_FMT " socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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, (char *)&ipv6, sizeof(ipv6), MSG_NOSIGNAL);
if (s != sizeof(ipv6))
{
if (s == SOCKET_ERROR)
DBG("couldn't recv ipv6 of connection to host=%s:%s fd=" SOCKET_FMT " socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, eina_error_msg_get(efl_net_socket_error_get()));
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=" SOCKET_FMT " 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=" SOCKET_FMT " 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 == INVALID_SOCKET); /* 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)
{
Efl_Net_Ip_Connect_Async_Data *d = data;
const char *host, *port, *proxy;
char *addrcopy;
char **proxies = NULL;
volatile int proxies_idx = 0;
volatile Eina_Bool is_libproxy = EINA_FALSE;
addrcopy = strdup(d->address);
if (!addrcopy)
{
d->error = errno;
return;
}
if (!efl_net_ip_port_split(addrcopy, &host, &port))
{
d->error = EINVAL;
free(addrcopy);
return;
}
if (!port) port = "0";
EINA_THREAD_CLEANUP_PUSH(free, addrcopy);
proxy = d->proxy;
ecore_con - move libproxy to a slave binary with stdin/out msging so here's the ugly problem. libproxy. yes. we've discussed memory usage (e.g. it may have to execute javascript and pull in lots of deps etc.) but we dlopene'd on the fly. ok... but this didn't solve another issue i hit: libproxy was causing enlightenment to abort(). some internal bit of libproxy was raising a c++ exception. this wasn't caught. this causes an abort(). takes down your entire desktop. FANTASTIC. this is bad. i wouldnt' expect a library we depend on to be THIS anti-social but libproxy seemingly is. it SHOULd catch its error sand just propagate back to us so we can handle gracefully. there reall is no way around this - isolate libproxy. it's even worse that libproxy can load arbitrary modules that come from anywhere sho who knows what issues this can cause. isolation is the best solution i can think of. so this makes an elf+net_proxy_helper we spawn the first time we need a proxy lookup. we re-use that binary again and again until it exits (it should exit after 10 seconds of being idle with no requests coming in/pending). it'll respawn again later if needed. this involves now the efl net threads having to marshall back to mainloop to do the spawn and to write to the proxy process (reading is done by async exe data events and the data is passed down a thread queue to the waitng efl net thread). if the exe dies with pending requests unanswered then it's respawned again and the req's are re-sent to it... just in case. it has a limit on how often it'll respawn quickly. this seems to work in my limited testing. this ALSO now isolates memory usage of libproxy to another slave process AND this process will die taking its memory with it once it's been idle for long enough. that;s also another good solution to keeping libproxy impact at bay.
2017-01-08 05:57:54 -08:00
if ((!proxy) && _efl_net_proxy_helper_can_do())
{
Eina_Stringshare *url;
url = eina_stringshare_printf("%s://%s:%s", d->protocol == IPPROTO_UDP ? "udp" : "tcp", host, port);
if (!url)
{
ERR("Could not assemble URL");
}
else
{
proxies = ecore_con_libproxy_proxies_get(url, thread);
eina_stringshare_del(url);
}
}
EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)ecore_con_libproxy_proxies_free, proxies);
next_proxy:
if ((!proxy) && (proxies) && (proxies_idx >= 0))
{
proxy = proxies[proxies_idx];
if (!proxy)
{
is_libproxy = EINA_FALSE;
proxies_idx = -1;
}
else
{
if (strcmp(proxy, "direct://") == 0)
{
/* give a chance to try envvars */
proxy = NULL;
is_libproxy = EINA_FALSE;
}
else
{
DBG("libproxy said %s for host='%s' port='%s'", proxy, host, port);
is_libproxy = EINA_TRUE;
}
proxies_idx++;
}
}
if (!proxy)
{
proxy = d->proxy_env;
if (!proxy)
proxy = "";
else
{
if (_efl_net_ip_no_proxy(host, d->no_proxy_strv))
proxy = "";
else
DBG("using proxy %s from envvar", proxy);
}
}
/* 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 (eina_str_has_prefix(proxy, "socks://"))
{
_efl_net_ip_connect_async_run_socks5(d, host, port, proxy + strlen("socks://"));
if (d->error)
_efl_net_ip_connect_async_run_socks4(d, host, port, proxy + strlen("socks://"));
}
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
{
if (is_libproxy)
{
DBG("libproxy said %s but it's not supported, try next proxy", proxy);
proxy = NULL;
goto next_proxy;
}
/* 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'))
{
if (is_libproxy)
{
DBG("libproxy said %s but it failed, try next proxy", proxy);
proxy = NULL;
goto next_proxy;
}
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);
EINA_THREAD_CLEANUP_POP(EINA_TRUE);
}
static void
_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_ip_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
{
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 != INVALID_SOCKET))
{
if (!eina_file_close_on_exec(d->sockfd, EINA_FALSE))
{
d->error = errno;
closesocket(d->sockfd);
d->sockfd = INVALID_SOCKET;
}
}
#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_ip_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Ip_Connect_Async_Data *d = data;
if (d->sockfd != INVALID_SOCKET) closesocket(d->sockfd);
_efl_net_ip_connect_async_data_free(d);
}
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)
{
Efl_Net_Ip_Connect_Async_Data *d;
EINA_SAFETY_ON_NULL_RETURN_VAL(address, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
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 = 0;
d->close_on_exec = close_on_exec;
d->type = type;
d->protocol = protocol;
d->sockfd = INVALID_SOCKET;
d->error = 0;
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;
}
static Eina_Error
efl_net_multicast_address4_parse(const char *address, struct ip_mreq *mreq)
{
char *str = NULL;
const char *iface;
Eina_Error err = 0;
int r;
iface = strchr(address, '@');
if (!iface) iface = "0.0.0.0";
else
{
str = malloc(iface - address + 1);
EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
memcpy(str, address, iface - address);
str[iface - address] = '\0';
address = str;
iface++;
if (iface[0] == '\0') iface = "0.0.0.0";
}
if (address[0] == '\0')
{
err = EINVAL;
goto end;
}
r = inet_pton(AF_INET, address, &mreq->imr_multiaddr);
if (r != 1)
{
if (r < 0) err = efl_net_socket_error_get();
else err = EINVAL;
goto end;
}
r = inet_pton(AF_INET, iface, &mreq->imr_interface);
if (r != 1)
{
if (r < 0) err = efl_net_socket_error_get();
else err = EINVAL;
goto end;
}
end:
free(str);
return err;
}
static Eina_Error
efl_net_multicast_address6_parse(const char *address, struct ipv6_mreq *mreq)
{
char *str;
char *endptr;
const char *iface;
Eina_Error err = 0;
unsigned long idx;
int r;
str = strdup(address);
EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
address = str;
if (address[0] == '[')
{
address++;
endptr = strchr(address, ']');
if (!endptr)
{
err = EINVAL;
goto end;
}
memmove(endptr, endptr + 1, strlen(endptr + 1) + 1);
}
iface = strchr(address, '@');
if (!iface) iface = "0";
else
{
str[iface - address] = '\0';
iface++;
if (iface[0] == '\0') iface = "0";
}
if (address[0] == '\0')
{
err = EINVAL;
goto end;
}
r = inet_pton(AF_INET6, address, &mreq->ipv6mr_multiaddr);
if (r != 1)
{
if (r < 0) err = efl_net_socket_error_get();
else err = EINVAL;
goto end;
}
errno = 0;
idx = strtoul(iface, &endptr, 10);
if (errno)
{
err = errno;
goto end;
}
else if ((iface == endptr) || (endptr[0] != '\0'))
{
errno = EINVAL;
goto end;
}
else if (idx > UINT32_MAX)
{
errno = ERANGE;
goto end;
}
mreq->ipv6mr_interface = idx;
end:
free(str);
return err;
}
Eina_Error
efl_net_multicast_join(SOCKET fd, int family, const char *address)
{
Eina_Error err;
if (family == AF_INET)
{
struct ip_mreq mreq;
err = efl_net_multicast_address4_parse(address, &mreq);
if (err)
return err;
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq)) == 0)
return 0;
}
else if (family == AF_INET6)
{
struct ipv6_mreq mreq;
err = efl_net_multicast_address6_parse(address, &mreq);
if (err)
return err;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&mreq, sizeof(mreq)) == 0)
return 0;
}
else
{
ERR("invalid socket family=%d", family);
return EINVAL;
}
return efl_net_socket_error_get();
}
Eina_Error
efl_net_multicast_leave(SOCKET fd, int family, const char *address)
{
Eina_Error err;
if (family == AF_INET)
{
struct ip_mreq mreq;
err = efl_net_multicast_address4_parse(address, &mreq);
if (err)
return err;
if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&mreq, sizeof(mreq)) == 0)
return 0;
}
else if (family == AF_INET6)
{
struct ipv6_mreq mreq;
err = efl_net_multicast_address6_parse(address, &mreq);
if (err)
return err;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (const char *)&mreq, sizeof(mreq)) == 0)
return 0;
}
else
{
ERR("invalid socket family=%d", family);
return EINVAL;
}
return efl_net_socket_error_get();
}
Eina_Error
efl_net_multicast_ttl_set(SOCKET fd, int family, uint8_t ttl)
{
int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
int opt = (family == AF_INET) ? IP_MULTICAST_TTL : IPV6_MULTICAST_HOPS;
#ifdef _WIN32
DWORD value = ttl;
#else
int value = ttl;
#endif
if (setsockopt(fd, level, opt, (const char *)&value, sizeof(value)) == 0)
return 0;
return efl_net_socket_error_get();
}
Eina_Error
efl_net_multicast_ttl_get(SOCKET fd, int family, uint8_t *ttl)
{
int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
int opt = (family == AF_INET) ? IP_MULTICAST_TTL : IPV6_MULTICAST_HOPS;
#ifdef _WIN32
DWORD value;
int valuelen = sizeof(value);
#else
int value;
socklen_t valuelen = sizeof(value);
#endif
if (getsockopt(fd, level, opt, (char *)&value, &valuelen) == 0)
{
*ttl = value;
return 0;
}
return efl_net_socket_error_get();
}
Eina_Error
efl_net_multicast_loopback_set(SOCKET fd, int family, Eina_Bool loopback)
{
int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
int opt = (family == AF_INET) ? IP_MULTICAST_LOOP : IPV6_MULTICAST_LOOP;
#ifdef _WIN32
DWORD value = loopback;
#else
int value = loopback;
#endif
if (setsockopt(fd, level, opt, (const char *)&value, sizeof(value)) == 0)
return 0;
return efl_net_socket_error_get();
}
Eina_Error
efl_net_multicast_loopback_get(SOCKET fd, int family, Eina_Bool *loopback)
{
int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
int opt = (family == AF_INET) ? IP_MULTICAST_LOOP : IPV6_MULTICAST_LOOP;
#ifdef _WIN32
DWORD value;
int valuelen = sizeof(value);
#else
int value;
socklen_t valuelen = sizeof(value);
#endif
if (getsockopt(fd, level, opt, (char *)&value, &valuelen) == 0)
{
*loopback = !!value;
return 0;
}
return efl_net_socket_error_get();
}
size_t
efl_net_udp_datagram_size_query(SOCKET fd)
{
#ifdef _WIN32
unsigned long size;
if (ioctlsocket(fd, FIONREAD, &size) == 0)
return size;
#else
int size;
if (ioctl(fd, FIONREAD, &size) == 0)
return size;
#endif
return READBUFSIZ;
}
char **
ecore_con_libproxy_proxies_get(const char *url, Ecore_Thread *eth)
{
int id = _efl_net_proxy_helper_url_req_send(url, eth);
ecore_con - move libproxy to a slave binary with stdin/out msging so here's the ugly problem. libproxy. yes. we've discussed memory usage (e.g. it may have to execute javascript and pull in lots of deps etc.) but we dlopene'd on the fly. ok... but this didn't solve another issue i hit: libproxy was causing enlightenment to abort(). some internal bit of libproxy was raising a c++ exception. this wasn't caught. this causes an abort(). takes down your entire desktop. FANTASTIC. this is bad. i wouldnt' expect a library we depend on to be THIS anti-social but libproxy seemingly is. it SHOULd catch its error sand just propagate back to us so we can handle gracefully. there reall is no way around this - isolate libproxy. it's even worse that libproxy can load arbitrary modules that come from anywhere sho who knows what issues this can cause. isolation is the best solution i can think of. so this makes an elf+net_proxy_helper we spawn the first time we need a proxy lookup. we re-use that binary again and again until it exits (it should exit after 10 seconds of being idle with no requests coming in/pending). it'll respawn again later if needed. this involves now the efl net threads having to marshall back to mainloop to do the spawn and to write to the proxy process (reading is done by async exe data events and the data is passed down a thread queue to the waitng efl net thread). if the exe dies with pending requests unanswered then it's respawned again and the req's are re-sent to it... just in case. it has a limit on how often it'll respawn quickly. this seems to work in my limited testing. this ALSO now isolates memory usage of libproxy to another slave process AND this process will die taking its memory with it once it's been idle for long enough. that;s also another good solution to keeping libproxy impact at bay.
2017-01-08 05:57:54 -08:00
if (id < 0) return NULL;
return _efl_net_proxy_helper_url_wait(id);
}
void
ecore_con_libproxy_proxies_free(char **proxies)
{
char **itr;
if (!proxies) return;
for (itr = proxies; *itr != NULL; itr++)
free(*itr);
free(proxies);
}