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"),
|
"If set will limit number of clients to accept"),
|
||||||
ECORE_GETOPT_STORE_BOOL('r', "clients-reject-excess",
|
ECORE_GETOPT_STORE_BOOL('r', "clients-reject-excess",
|
||||||
"If true, excess clients will be immediately rejected."),
|
"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_VERSION('V', "version"),
|
||||||
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
|
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
|
||||||
ECORE_GETOPT_LICENSE('L', "license"),
|
ECORE_GETOPT_LICENSE('L', "license"),
|
||||||
|
@ -453,11 +455,13 @@ main(int argc, char **argv)
|
||||||
char *address = NULL;
|
char *address = NULL;
|
||||||
unsigned int clients_limit = 0;
|
unsigned int clients_limit = 0;
|
||||||
Eina_Bool clients_reject_excess = EINA_FALSE;
|
Eina_Bool clients_reject_excess = EINA_FALSE;
|
||||||
|
Eina_Bool ipv6_only = EINA_TRUE;
|
||||||
Eina_Bool quit_option = EINA_FALSE;
|
Eina_Bool quit_option = EINA_FALSE;
|
||||||
Ecore_Getopt_Value values[] = {
|
Ecore_Getopt_Value values[] = {
|
||||||
ECORE_GETOPT_VALUE_BOOL(echo),
|
ECORE_GETOPT_VALUE_BOOL(echo),
|
||||||
ECORE_GETOPT_VALUE_UINT(clients_limit),
|
ECORE_GETOPT_VALUE_UINT(clients_limit),
|
||||||
ECORE_GETOPT_VALUE_BOOL(clients_reject_excess),
|
ECORE_GETOPT_VALUE_BOOL(clients_reject_excess),
|
||||||
|
ECORE_GETOPT_VALUE_BOOL(ipv6_only),
|
||||||
|
|
||||||
/* standard block to provide version, copyright, license and help */
|
/* standard block to provide version, copyright, license and help */
|
||||||
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
|
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
|
||||||
|
@ -518,6 +522,9 @@ main(int argc, char **argv)
|
||||||
goto end;
|
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
|
/* an explicit call to efl_net_server_serve() after the object is
|
||||||
* constructed allows for more complex setup, such as interacting
|
* constructed allows for more complex setup, such as interacting
|
||||||
* with the object to add more properties that couldn't be done
|
* with the object to add more properties that couldn't be done
|
||||||
|
|
|
@ -29,8 +29,13 @@
|
||||||
|
|
||||||
#define MY_CLASS EFL_NET_SERVER_TCP_CLASS
|
#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
|
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 = {};
|
struct sockaddr_storage addr = {};
|
||||||
char *str, *host, *port;
|
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);
|
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,
|
fd = efl_net_socket4(addr.ss_family, SOCK_STREAM, IPPROTO_TCP,
|
||||||
efl_net_server_fd_close_on_exec_get(o));
|
efl_net_server_fd_close_on_exec_get(o));
|
||||||
if (fd < 0)
|
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);
|
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);
|
r = bind(fd, (struct sockaddr *)&addr, addrlen);
|
||||||
if (r < 0)
|
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;
|
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);
|
r = listen(fd, 0);
|
||||||
if (r < 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 });
|
{ EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_tcp_client_event_closed });
|
||||||
|
|
||||||
static void
|
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,
|
Eo *client = efl_add(EFL_NET_SOCKET_TCP_CLASS, o,
|
||||||
efl_event_callback_array_add(efl_added, _efl_net_server_tcp_client_cbs(), 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
|
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;
|
struct sockaddr_storage addr;
|
||||||
socklen_t addrlen;
|
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);
|
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"
|
#include "efl_net_server_tcp.eo.c"
|
||||||
|
|
|
@ -4,7 +4,34 @@ class Efl.Net.Server.Tcp (Efl.Net.Server.Fd) {
|
||||||
@since 1.19
|
@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 {
|
implements {
|
||||||
Efl.Net.Server.serve;
|
Efl.Net.Server.serve;
|
||||||
|
|
Loading…
Reference in New Issue