From cb8695e9d65500b76ac4d3aa124adf4bbe749298 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Fri, 9 Sep 2016 20:09:51 -0300 Subject: [PATCH] efl_net_dialer_tcp: make asynchronous resolve and connect. both resolve (getaddrinfo()) and connect() are now done in Ecore_Thread, avoid to block the main loop. My plan is to always use the threaded connect() using a blocking socket, only set it to non-blocking after the socket is returned to the main thread and before it's accessible to the user. It will make the connect behavior more uniform. Some errors were moved from HTTP to Dialer as they are more generic. --- src/lib/ecore_con/Ecore_Con_Eo.h | 9 +- src/lib/ecore_con/ecore_con.c | 262 +++++++++++++++++++++++++++ src/lib/ecore_con/ecore_con_private.h | 62 +++++++ src/lib/ecore_con/ecore_con_url_curl.c | 13 +- src/lib/ecore_con/efl_net_dialer.eo | 4 + src/lib/ecore_con/efl_net_dialer_tcp.c | 229 +++++++++++++++-------- src/lib/ecore_con/efl_net_dialer_websocket.c | 4 +- src/lib/ecore_con/efl_net_http_types.eot | 3 - 8 files changed, 498 insertions(+), 88 deletions(-) diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h index 6028d60..f10d1d7 100644 --- a/src/lib/ecore_con/Ecore_Con_Eo.h +++ b/src/lib/ecore_con/Ecore_Con_Eo.h @@ -16,6 +16,12 @@ #include "efl_net_server_tcp.eo.h" #include "efl_net_http_types.eot.h" + +/* TODO: should be generated from 'var Efl.Net.Dialer.Error.*' */ +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; + /* TODO: should be generated from 'var Efl.Net.Http.Error.*' */ extern Eina_Error EFL_NET_HTTP_ERROR_BAD_CONTENT_ENCODING; extern Eina_Error EFL_NET_HTTP_ERROR_BAD_DOWNLOAD_RESUME; @@ -23,9 +29,6 @@ extern Eina_Error EFL_NET_HTTP_ERROR_BAD_FUNCTION_ARGUMENT; extern Eina_Error EFL_NET_HTTP_ERROR_CHUNK_FAILED; extern Eina_Error EFL_NET_HTTP_ERROR_CONV_FAILED; extern Eina_Error EFL_NET_HTTP_ERROR_CONV_REQD; -extern Eina_Error EFL_NET_HTTP_ERROR_COULDNT_CONNECT; -extern Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_HOST; -extern Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_PROXY; extern Eina_Error EFL_NET_HTTP_ERROR_FAILED_INIT; extern Eina_Error EFL_NET_HTTP_ERROR_FILE_COULDNT_READ_FILE; extern Eina_Error EFL_NET_HTTP_ERROR_FILESIZE_EXCEEDED; diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c index 161434a..63f2c8e 100644 --- a/src/lib/ecore_con/ecore_con.c +++ b/src/lib/ecore_con/ecore_con.c @@ -175,6 +175,10 @@ EAPI int ECORE_CON_EVENT_CLIENT_ERROR = 0; EAPI int ECORE_CON_EVENT_SERVER_ERROR = 0; 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; + static Eina_List *servers = NULL; static int _ecore_con_init_count = 0; static int _ecore_con_event_count = 0; @@ -215,6 +219,10 @@ ecore_con_init(void) ECORE_CON_EVENT_SERVER_ERROR = ecore_event_type_new(); ECORE_CON_EVENT_PROXY_BIND = ecore_event_type_new(); + 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"); + eina_magic_string_set(ECORE_MAGIC_CON_SERVER, "Ecore_Con_Server"); eina_magic_string_set(ECORE_MAGIC_CON_CLIENT, "Ecore_Con_Client"); eina_magic_string_set(ECORE_MAGIC_CON_URL, "Ecore_Con_Url"); @@ -3037,6 +3045,46 @@ efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr) 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++; + } + } + + *p_host = host; + *p_port = port; + return EINA_TRUE; +} + + int efl_net_socket4(int domain, int type, int protocol, Eina_Bool close_on_exec) { @@ -3065,3 +3113,217 @@ efl_net_socket4(int domain, int type, int protocol, Eina_Bool close_on_exec) return fd; } + +typedef struct _Efl_Net_Resolve_Async_Data +{ + Efl_Net_Resolve_Async_Cb cb; + const void *data; + char *host; + char *port; + struct addrinfo *result; + struct addrinfo *hints; + int gai_error; +} Efl_Net_Resolve_Async_Data; + +static void +_efl_net_resolve_async_run(void *data, Ecore_Thread *thread) +{ + Efl_Net_Resolve_Async_Data *d = data; + + while (!ecore_thread_check(thread)) + { + 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; + + DBG("getaddrinfo(\"%s\", \"%s\") failed: %s", d->host, d->port, gai_strerror(d->gai_error)); + break; + } + + 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_resolve_async_data_free(Efl_Net_Resolve_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_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); +} + +static void +_efl_net_resolve_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); +} + +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_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_Resolve_Async_Data)); + EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL); + + d->cb = cb; + d->data = data; + d->host = strdup(host); + EINA_SAFETY_ON_NULL_GOTO(d->host, failed_host); + d->port = strdup(port); + EINA_SAFETY_ON_NULL_GOTO(d->port, failed_port); + + if (!hints) d->hints = NULL; + else + { + d->hints = malloc(sizeof(struct addrinfo)); + EINA_SAFETY_ON_NULL_GOTO(d->hints, failed_hints); + memcpy(d->hints, hints, sizeof(struct addrinfo)); + } + + d->result = NULL; + + return ecore_thread_run(_efl_net_resolve_async_run, + _efl_net_resolve_async_end, + _efl_net_resolve_async_cancel, + d); + + failed_hints: + free(d->port); + failed_port: + free(d->host); + failed_host: + free(d); + return NULL; +} + +typedef struct _Efl_Net_Connect_Async_Data +{ + Efl_Net_Connect_Async_Cb cb; + const void *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_connect_async_run(void *data, Ecore_Thread *thread) +{ + Efl_Net_Connect_Async_Data *d = data; + char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = ""; + int r; + + d->error = 0; + + d->sockfd = efl_net_socket4(d->addr->sa_family, d->type, d->protocol, d->close_on_exec); + 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; + } + + if (ecore_thread_check(thread)) + { + d->error = ECANCELED; + close(d->sockfd); + d->sockfd = -1; + return; + } + + 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) + { + d->error = errno; + close(d->sockfd); + d->sockfd = -1; + DBG("connect(%d, %s) failed: %s", d->sockfd, buf, strerror(errno)); + return; + } + + DBG("connected fd=%d 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; + 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 >= 0) close(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 = -1; + d->error = 0; + + return ecore_thread_run(_efl_net_connect_async_run, + _efl_net_connect_async_end, + _efl_net_connect_async_cancel, + d); +} diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h index 69102d7..94493b0 100644 --- a/src/lib/ecore_con/ecore_con_private.h +++ b/src/lib/ecore_con/ecore_con_private.h @@ -375,8 +375,70 @@ void ecore_con_mempool_shutdown(void); Eina_Bool efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr); +/** + * @brief splits an address in the format "host:port" in two + * null-terminated strings. + * + * The address may be 'server.com:1234', 'server.com:http', + * 'server.com' (@c *p_port will be NULL), IPv4 127.0.0.1:456 or + * IPv6 [::1]:456 + * + * @param[inout] buf contains the string to be split and will be modified. + * @param[out] p_host returns a pointer inside @a buf with + * null-terminated host part. + * @param[out] p_port returns a pointer with null-terminated port + * part. The pointer may be inside @a buf if port was + * specified or #NULL if it wasn't specified. + * + * @return #EINA_TRUE on success, #EINA_FALSE on errors. + * + * @internal + */ +Eina_Bool efl_net_ip_port_split(char *buf, const char **p_host, const char **p_port); + 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 + * must close(). + * + * @internal + */ +typedef void (*Efl_Net_Connect_Async_Cb)(void *data, const struct sockaddr *addr, socklen_t addrlen, int sockfd, Eina_Error error); + +/** + * @brief asynchronously create a socket and connect to the address. + * + * This will call socket() and connect() 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_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); + static inline Eina_Error efl_net_socket_error_get(void) { diff --git a/src/lib/ecore_con/ecore_con_url_curl.c b/src/lib/ecore_con/ecore_con_url_curl.c index e28326e..1cb241b 100644 --- a/src/lib/ecore_con/ecore_con_url_curl.c +++ b/src/lib/ecore_con/ecore_con_url_curl.c @@ -32,9 +32,6 @@ EAPI Eina_Error EFL_NET_HTTP_ERROR_BAD_FUNCTION_ARGUMENT = 0; EAPI Eina_Error EFL_NET_HTTP_ERROR_CHUNK_FAILED = 0; EAPI Eina_Error EFL_NET_HTTP_ERROR_CONV_FAILED = 0; EAPI Eina_Error EFL_NET_HTTP_ERROR_CONV_REQD = 0; -EAPI Eina_Error EFL_NET_HTTP_ERROR_COULDNT_CONNECT = 0; -EAPI Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_HOST = 0; -EAPI Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_PROXY = 0; EAPI Eina_Error EFL_NET_HTTP_ERROR_FAILED_INIT = 0; EAPI Eina_Error EFL_NET_HTTP_ERROR_FILE_COULDNT_READ_FILE = 0; EAPI Eina_Error EFL_NET_HTTP_ERROR_FILESIZE_EXCEEDED = 0; @@ -90,6 +87,10 @@ _curlcode_to_eina_error(const CURLcode code) case CURLE_AGAIN: return EAGAIN; case CURLE_OUT_OF_MEMORY: return ENOMEM; + case CURLE_COULDNT_CONNECT: return EFL_NET_DIALER_ERROR_COULDNT_CONNECT; + case CURLE_COULDNT_RESOLVE_PROXY: return EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY; + case CURLE_COULDNT_RESOLVE_HOST: return EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST; + #define _MAP(n) case CURLE_ ## n: return EFL_NET_HTTP_ERROR_ ## n _MAP(BAD_CONTENT_ENCODING); @@ -98,9 +99,6 @@ _curlcode_to_eina_error(const CURLcode code) _MAP(CHUNK_FAILED); _MAP(CONV_FAILED); _MAP(CONV_REQD); - _MAP(COULDNT_CONNECT); - _MAP(COULDNT_RESOLVE_HOST); - _MAP(COULDNT_RESOLVE_PROXY); _MAP(FAILED_INIT); _MAP(FILE_COULDNT_READ_FILE); _MAP(FILESIZE_EXCEEDED); @@ -192,9 +190,6 @@ _c_init_errors(void) _MAP(CHUNK_FAILED); _MAP(CONV_FAILED); _MAP(CONV_REQD); - _MAP(COULDNT_CONNECT); - _MAP(COULDNT_RESOLVE_HOST); - _MAP(COULDNT_RESOLVE_PROXY); _MAP(FAILED_INIT); _MAP(FILE_COULDNT_READ_FILE); _MAP(FILESIZE_EXCEEDED); diff --git a/src/lib/ecore_con/efl_net_dialer.eo b/src/lib/ecore_con/efl_net_dialer.eo index d2766f5..74b5682 100644 --- a/src/lib/ecore_con/efl_net_dialer.eo +++ b/src/lib/ecore_con/efl_net_dialer.eo @@ -1,3 +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; + 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 88f78fe..002f3c6 100644 --- a/src/lib/ecore_con/efl_net_dialer_tcp.c +++ b/src/lib/ecore_con/efl_net_dialer_tcp.c @@ -31,117 +31,203 @@ typedef struct _Efl_Net_Dialer_Tcp_Data { + struct { + Ecore_Thread *thread; + struct addrinfo *names; + struct addrinfo *current; + } resolve; + struct { + Ecore_Thread *thread; + } connect; Eina_Stringshare *address_dial; Eina_Stringshare *proxy; Eina_Bool connected; + Eina_Bool closed; double timeout_dial; } Efl_Net_Dialer_Tcp_Data; EOLIAN static void _efl_net_dialer_tcp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Tcp_Data *pd) { + if (pd->connect.thread) + { + ecore_thread_cancel(pd->connect.thread); + 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); eina_stringshare_replace(&pd->proxy, NULL); } -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) +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) { - struct sockaddr_storage addr = {}; - char *str, *host, *port; - int r, fd; - socklen_t addrlen; - char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")]; + Eo *o = data; + Efl_Net_Dialer_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS); + const struct addrinfo *addrinfo; - 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); + pd->connect.thread = NULL; - // TODO: change to getaddrinfo() and move to a thread... - str = host = strdup(address); - EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM); + 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 (host[0] == '[') + if (!pd->resolve.current) return; + + pd->resolve.current = pd->resolve.current->ai_next; + if (!pd->resolve.current) { - struct sockaddr_in6 *a = (struct sockaddr_in6 *)&addr; - /* IPv6 is: [IP]:port */ - host++; - port = strchr(host, ']'); - if (!port) + freeaddrinfo(pd->resolve.names); + pd->resolve.names = NULL; + if (err) + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); + else { - ERR("missing ']' in IPv6 address: %s", address); - goto invalid_address; + err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT; + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); } - *port = '\0'; - port++; - - if (port[0] == ':') - port++; - else - port = NULL; - a->sin6_family = AF_INET6; - a->sin6_port = htons(port ? atoi(port) : 0); - r = inet_pton(AF_INET6, host, &(a->sin6_addr)); - addrlen = sizeof(*a); + return; } - else + + addrinfo = pd->resolve.current; + pd->connect.thread = efl_net_connect_async_new(addrinfo->ai_addr, + addrinfo->ai_addrlen, + SOCK_STREAM, + IPPROTO_TCP, + efl_net_socket_fd_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 */ + + addrinfo = pd->resolve.current; + + efl_net_socket_fd_family_set(o, addrinfo->ai_family); + if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr)) { - struct sockaddr_in *a = (struct sockaddr_in *)&addr; - port = strchr(host, ':'); - if (port) - { - *port = '\0'; - port++; - } - a->sin_family = AF_INET; - a->sin_port = htons(port ? atoi(port) : 0); - r = inet_pton(AF_INET, host, &(a->sin_addr)); - addrlen = sizeof(*a); + efl_net_socket_address_remote_set(o, buf); + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); } - if (r != 1) + 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_net_socket_fd_close_on_exec_get(o), + _efl_net_dialer_tcp_connected, + o); + end: + 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) +{ + Eo *o = data; + Efl_Net_Dialer_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS); + + pd->resolve.thread = NULL; + + if (gai_error) { - ERR("could not parse IP '%s' (%s)", host, address); - goto invalid_address; + Eina_Error err = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST; + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); + if (result) freeaddrinfo(result); + return; } - free(str); - efl_net_socket_fd_family_set(o, addr.ss_family); - efl_net_dialer_address_dial_set(o, address); + if (pd->resolve.names) freeaddrinfo(pd->resolve.names); + pd->resolve.names = result; + pd->resolve.current = result; + + _efl_net_dialer_tcp_connect(o, pd); +} + +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; + + 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_fmt(buf, sizeof(buf), (struct sockaddr *)&addr)) + if (!efl_net_ip_port_split(str, &host, &port)) { - efl_net_socket_address_remote_set(o, buf); - efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); + ERR("could not parse IP:PORT '%s'", address); + free(str); + return EINVAL; } + if (strchr(host, ':')) hints.ai_family = AF_INET6; + if (!port) port = "0"; - fd = efl_net_socket4(addr.ss_family, SOCK_STREAM, IPPROTO_TCP, efl_net_socket_fd_close_on_exec_get(o)); - if (fd < 0) + if (pd->connect.thread) { - ERR("socket(%d, SOCK_STREAM, IPPROTO_TCP): %s", - addr.ss_family, strerror(errno)); - return errno; + ecore_thread_cancel(pd->connect.thread); + pd->connect.thread = NULL; } - r = connect(fd, (struct sockaddr *)&addr, addrlen); - if (r < 0) + if (pd->resolve.names) { - int errno_bkp = errno; - ERR("connect(%d, %s): %s", fd, address, strerror(errno)); - close(fd); - return errno_bkp; + 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 0; + if (pd->resolve.thread) + ecore_thread_cancel(pd->resolve.thread); - invalid_address: + pd->resolve.thread = efl_net_resolve_async_new(host, port, &hints, _efl_net_dialer_tcp_resolved, o); free(str); - return EINVAL; + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->resolve.thread, EINVAL); + + efl_net_dialer_address_dial_set(o, address); + + return 0; } EOLIAN static void @@ -197,8 +283,9 @@ _efl_net_dialer_tcp_efl_net_dialer_connected_get(Eo *o EINA_UNUSED, Efl_Net_Dial } EOLIAN static Eina_Error -_efl_net_dialer_tcp_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Tcp_Data *pd EINA_UNUSED) +_efl_net_dialer_tcp_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Tcp_Data *pd) { + pd->closed = EINA_TRUE; efl_net_dialer_connected_set(o, EINA_FALSE); return efl_io_closer_close(efl_super(o, MY_CLASS)); } diff --git a/src/lib/ecore_con/efl_net_dialer_websocket.c b/src/lib/ecore_con/efl_net_dialer_websocket.c index cec03e8..8364079 100644 --- a/src/lib/ecore_con/efl_net_dialer_websocket.c +++ b/src/lib/ecore_con/efl_net_dialer_websocket.c @@ -831,7 +831,7 @@ _efl_net_dialer_websocket_http_headers_done(void *data, const Efl_Event *event E status = efl_net_dialer_http_response_status_get(pd->http); if (status != EFL_NET_HTTP_STATUS_SWITCHING_PROTOCOLS) { - Eina_Error err = EFL_NET_HTTP_ERROR_COULDNT_CONNECT; + Eina_Error err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT; if ((status >= 300) && (status < 400)) return; WRN("failed WebSocket handshake: HTTP status=%d, expected=%d", @@ -871,7 +871,7 @@ _efl_net_dialer_websocket_http_headers_done(void *data, const Efl_Event *event E if ((!upgraded) || (!connection_websocket) || (!accepted)) { - Eina_Error err = EFL_NET_HTTP_ERROR_COULDNT_CONNECT; + Eina_Error err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT; WRN("failed WebSocket handshake: upgraded=%d, connection_websocket=%d, accepted=%d", upgraded, connection_websocket, accepted); efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); diff --git a/src/lib/ecore_con/efl_net_http_types.eot b/src/lib/ecore_con/efl_net_http_types.eot index 6fb6911..bd47012 100644 --- a/src/lib/ecore_con/efl_net_http_types.eot +++ b/src/lib/ecore_con/efl_net_http_types.eot @@ -126,9 +126,6 @@ var @extern Efl.Net.Http.Error.BAD_FUNCTION_ARGUMENT: Eina.Error; var @extern Efl.Net.Http.Error.CHUNK_FAILED: Eina.Error; var @extern Efl.Net.Http.Error.CONV_FAILED: Eina.Error; var @extern Efl.Net.Http.Error.CONV_REQD: Eina.Error; -var @extern Efl.Net.Http.Error.COULDNT_CONNECT: Eina.Error; -var @extern Efl.Net.Http.Error.COULDNT_RESOLVE_HOST: Eina.Error; -var @extern Efl.Net.Http.Error.COULDNT_RESOLVE_PROXY: Eina.Error; var @extern Efl.Net.Http.Error.FAILED_INIT: Eina.Error; var @extern Efl.Net.Http.Error.FILE_COULDNT_READ_FILE: Eina.Error; var @extern Efl.Net.Http.Error.FILESIZE_EXCEEDED: Eina.Error; -- 2.7.4