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.
This commit is contained in:
Gustavo Sverzut Barbieri 2016-10-18 21:24:16 -02:00
parent 1809b3b959
commit c873703c41
3 changed files with 100 additions and 7 deletions

View File

@ -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

View File

@ -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"

View File

@ -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;