forked from enlightenment/efl
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:
parent
1809b3b959
commit
c873703c41
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue