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