From c873703c416d92ba90a21e38301b07826c0e9dcc Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 18 Oct 2016 21:24:16 -0200 Subject: [PATCH] efl_net_server_tcp: allow IPv4 over IPv6 sockets. Sometimes we want to handle both IPv4 and IPv6 in the same socket, instead of spawning 2 servers, one for each protocol. That is achieved by means of disabling IPV6_V6ONLY socket option, present in most recent platforms. --- src/examples/ecore/efl_net_server_example.c | 7 +++ src/lib/ecore_con/efl_net_server_tcp.c | 71 ++++++++++++++++++++++++++--- src/lib/ecore_con/efl_net_server_tcp.eo | 29 +++++++++++- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/examples/ecore/efl_net_server_example.c b/src/examples/ecore/efl_net_server_example.c index 2ffcdee..cad337c 100644 --- a/src/examples/ecore/efl_net_server_example.c +++ b/src/examples/ecore/efl_net_server_example.c @@ -430,6 +430,8 @@ static const Ecore_Getopt options = { "If set will limit number of clients to accept"), ECORE_GETOPT_STORE_BOOL('r', "clients-reject-excess", "If true, excess clients will be immediately rejected."), + ECORE_GETOPT_STORE_BOOL(0, "ipv6-only", + "If true (default), only IPv6 clients will be allowed for a server if an IPv6 was used, otherwise IPv4 clients will be automatically converted into IPv6 and handled transparently."), ECORE_GETOPT_VERSION('V', "version"), ECORE_GETOPT_COPYRIGHT('C', "copyright"), ECORE_GETOPT_LICENSE('L', "license"), @@ -453,11 +455,13 @@ main(int argc, char **argv) char *address = NULL; unsigned int clients_limit = 0; Eina_Bool clients_reject_excess = EINA_FALSE; + Eina_Bool ipv6_only = EINA_TRUE; Eina_Bool quit_option = EINA_FALSE; Ecore_Getopt_Value values[] = { ECORE_GETOPT_VALUE_BOOL(echo), ECORE_GETOPT_VALUE_UINT(clients_limit), ECORE_GETOPT_VALUE_BOOL(clients_reject_excess), + ECORE_GETOPT_VALUE_BOOL(ipv6_only), /* standard block to provide version, copyright, license and help */ ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */ @@ -518,6 +522,9 @@ main(int argc, char **argv) goto end; } + if (cls == EFL_NET_SERVER_TCP_CLASS) + efl_net_server_tcp_ipv6_only_set(server, ipv6_only); + /* an explicit call to efl_net_server_serve() after the object is * constructed allows for more complex setup, such as interacting * with the object to add more properties that couldn't be done diff --git a/src/lib/ecore_con/efl_net_server_tcp.c b/src/lib/ecore_con/efl_net_server_tcp.c index ab39d60..8a2cf73 100644 --- a/src/lib/ecore_con/efl_net_server_tcp.c +++ b/src/lib/ecore_con/efl_net_server_tcp.c @@ -29,8 +29,13 @@ #define MY_CLASS EFL_NET_SERVER_TCP_CLASS +typedef struct _Efl_Net_Server_Tcp_Data +{ + Eina_Bool ipv6_only; +} Efl_Net_Server_Tcp_Data; + EOLIAN static Eina_Error -_efl_net_server_tcp_efl_net_server_serve(Eo *o, void *pd EINA_UNUSED, const char *address) +_efl_net_server_tcp_efl_net_server_serve(Eo *o, Efl_Net_Server_Tcp_Data *pd, const char *address) { struct sockaddr_storage addr = {}; char *str, *host, *port; @@ -94,9 +99,6 @@ _efl_net_server_tcp_efl_net_server_serve(Eo *o, void *pd EINA_UNUSED, const char efl_net_server_fd_family_set(o, addr.ss_family); - if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr)) - efl_net_server_address_set(o, buf); - fd = efl_net_socket4(addr.ss_family, SOCK_STREAM, IPPROTO_TCP, efl_net_server_fd_close_on_exec_get(o)); if (fd < 0) @@ -109,6 +111,10 @@ _efl_net_server_tcp_efl_net_server_serve(Eo *o, void *pd EINA_UNUSED, const char efl_loop_fd_set(o, fd); + /* apply pending value BEFORE bind() */ + if (addr.ss_family == AF_INET6) + efl_net_server_tcp_ipv6_only_set(o, pd->ipv6_only); + r = bind(fd, (struct sockaddr *)&addr, addrlen); if (r < 0) { @@ -117,6 +123,14 @@ _efl_net_server_tcp_efl_net_server_serve(Eo *o, void *pd EINA_UNUSED, const char goto error_listen; } + if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0) + { + ERR("getsockname(%d): %s", fd, strerror(errno)); + goto error_listen; + } + 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) { @@ -158,7 +172,7 @@ EFL_CALLBACKS_ARRAY_DEFINE(_efl_net_server_tcp_client_cbs, { EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_tcp_client_event_closed }); static void -_efl_net_server_tcp_efl_net_server_fd_client_add(Eo *o, void *pd EINA_UNUSED, int client_fd) +_efl_net_server_tcp_efl_net_server_fd_client_add(Eo *o, Efl_Net_Server_Tcp_Data *pd EINA_UNUSED, int client_fd) { Eo *client = efl_add(EFL_NET_SOCKET_TCP_CLASS, o, efl_event_callback_array_add(efl_added, _efl_net_server_tcp_client_cbs(), o), @@ -184,7 +198,7 @@ _efl_net_server_tcp_efl_net_server_fd_client_add(Eo *o, void *pd EINA_UNUSED, in } static void -_efl_net_server_tcp_efl_net_server_fd_client_reject(Eo *o, void *pd EINA_UNUSED, int client_fd) +_efl_net_server_tcp_efl_net_server_fd_client_reject(Eo *o, Efl_Net_Server_Tcp_Data *pd EINA_UNUSED, int client_fd) { struct sockaddr_storage addr; socklen_t addrlen; @@ -200,4 +214,49 @@ _efl_net_server_tcp_efl_net_server_fd_client_reject(Eo *o, void *pd EINA_UNUSED, efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, str); } +EOLIAN void +_efl_net_server_tcp_ipv6_only_set(Eo *o, Efl_Net_Server_Tcp_Data *pd, Eina_Bool ipv6_only) +{ + Eina_Bool old = pd->ipv6_only; + int fd = efl_loop_fd_get(o); + int value = ipv6_only; + + pd->ipv6_only = ipv6_only; + + if (fd < 0) return; + if (efl_net_server_fd_family_get(o) != AF_INET6) return; + +#ifdef IPV6_V6ONLY + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value)) < 0) + { + ERR("could not set socket=%d IPV6_V6ONLY=%d: %s", fd, value, strerror(errno)); + pd->ipv6_only = old; + } +#endif +} + +EOLIAN Eina_Bool +_efl_net_server_tcp_ipv6_only_get(Eo *o EINA_UNUSED, Efl_Net_Server_Tcp_Data *pd) +{ +#ifdef IPV6_V6ONLY + int fd = efl_loop_fd_get(o); + int value = 0; + socklen_t size = sizeof(value); + + if (fd < 0) goto end; + if (efl_net_server_fd_family_get(o) != AF_INET6) goto end; + + if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &value, &size) < 0) + { + WRN("getsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY): %s", fd, strerror(errno)); + goto end; + } + pd->ipv6_only = !!value; + + end: +#endif + return pd->ipv6_only; +} + + #include "efl_net_server_tcp.eo.c" diff --git a/src/lib/ecore_con/efl_net_server_tcp.eo b/src/lib/ecore_con/efl_net_server_tcp.eo index 443e5bc..0627060 100644 --- a/src/lib/ecore_con/efl_net_server_tcp.eo +++ b/src/lib/ecore_con/efl_net_server_tcp.eo @@ -4,7 +4,34 @@ class Efl.Net.Server.Tcp (Efl.Net.Server.Fd) { @since 1.19 ]] - data: null; + methods { + @property ipv6_only { + [[Whenever IPv6 listen address will accept only same-family clients or will allow IPv4 to connect as well. + + Since Linux 2.4.21, Windows Vista and MacOS X these + control whenever a server that did bind to an IPv6 + address will accept only IPv6 clients or will also + accept IPv4 by automatically converting them in an IPv6 + address, allowing a single socket to handle both + protocols. + + If an IPv6 address was used in @Efl.Net.Server.address, + this property is $false and an IPv4 connects, then an + address such as [::ffff:IPv4]:PORT will be used, such as + [::ffff:192.168.0.2]:1234, where the IPv4 address can be + extracted. + + If an IPv4 address was used in @Efl.Net.Server.address, + this has no effect. + + Systems can configure their default value, usually true + (allows only IPv6 clients). + ]] + values { + ipv6_only: bool; + } + } + } implements { Efl.Net.Server.serve; -- 2.7.4