efl_net_server_tcp: allow IPv4 over IPv6 sockets.
authorGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Tue, 18 Oct 2016 23:24:16 +0000 (21:24 -0200)
committerGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Thu, 20 Oct 2016 20:49:41 +0000 (18:49 -0200)
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
src/lib/ecore_con/efl_net_server_tcp.c
src/lib/ecore_con/efl_net_server_tcp.eo

index 2ffcdee..cad337c 100644 (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
index ab39d60..8a2cf73 100644 (file)
 
 #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"
index 443e5bc..0627060 100644 (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;