[glib] GSocket: fix g_socket_bind() allow_reuse semantics



commit 547df5937cc3b821498e27eb55cebc1f414ce597
Author: Dan Winship <danw gnome org>
Date:   Sun Feb 17 15:11:18 2013 -0500

    GSocket: fix g_socket_bind() allow_reuse semantics
    
    With UDP sockets, g_socket_bind() with allow_reuse=TRUE on Linux
    behaved in a way that the documentation didn't suggest, and that
    didn't match other OSes. (Specifically, it allowed binding multiple
    multicast sockets to the same address.)
    
    Since this behavior is useful, and since allow_reuse didn't have any
    other meaning with UDP sockets, update the docs to reflect the Linux
    behavior, and make it do the same thing on non-Linux.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=689245

 gio/gsocket.c      |   67 +++++++++++++++++++++++++++++++-------------
 gio/tests/socket.c |   79 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 126 insertions(+), 20 deletions(-)
---
diff --git a/gio/gsocket.c b/gio/gsocket.c
index 8d13e12..a1b00a3 100644
--- a/gio/gsocket.c
+++ b/gio/gsocket.c
@@ -1853,14 +1853,20 @@ g_socket_listen (GSocket  *socket,
  * In certain situations, you may also want to bind a socket that will be
  * used to initiate connections, though this is not normally required.
  *
- * @allow_reuse should be %TRUE for server sockets (sockets that you will
- * eventually call g_socket_accept() on), and %FALSE for client sockets.
- * (Specifically, if it is %TRUE, then g_socket_bind() will set the
- * %SO_REUSEADDR flag on the socket, allowing it to bind @address even if
- * that address was previously used by another socket that has not yet been
- * fully cleaned-up by the kernel. Failing to set this flag on a server
- * socket may cause the bind call to return %G_IO_ERROR_ADDRESS_IN_USE if
- * the server program is stopped and then immediately restarted.)
+ * If @socket is a TCP socket, then @allow_reuse controls the setting
+ * of the <literal>SO_REUSEADDR</literal> socket option; normally it
+ * should be %TRUE for server sockets (sockets that you will
+ * eventually call g_socket_accept() on), and %FALSE for client
+ * sockets. (Failing to set this flag on a server socket may cause
+ * g_socket_bind() to return %G_IO_ERROR_ADDRESS_IN_USE if the server
+ * program is stopped and then immediately restarted.)
+ *
+ * If @socket is a UDP socket, then @allow_reuse determines whether or
+ * not other UDP sockets can be bound to the same address at the same
+ * time. In particular, you can have several UDP sockets bound to the
+ * same address, and they will all receive all of the multicast and
+ * broadcast packets sent to that address. (The behavior of unicast
+ * UDP packets to an address with multiple listeners is not defined.)
  *
  * Returns: %TRUE on success, %FALSE on error.
  *
@@ -1873,27 +1879,48 @@ g_socket_bind (GSocket         *socket,
               GError         **error)
 {
   struct sockaddr_storage addr;
+  gboolean so_reuseaddr;
+#ifdef SO_REUSEPORT
+  gboolean so_reuseport;
+#endif
 
   g_return_val_if_fail (G_IS_SOCKET (socket) && G_IS_SOCKET_ADDRESS (address), FALSE);
 
   if (!check_socket (socket, error))
     return FALSE;
 
-  /* SO_REUSEADDR on Windows means something else and is not what we want.
-     It always allows the unix variant of SO_REUSEADDR anyway */
-#ifndef G_OS_WIN32
-  {
-    reuse_address = !!reuse_address;
-    /* Ignore errors here, the only likely error is "not supported", and
-       this is a "best effort" thing mainly */
-    g_socket_set_option (socket, SOL_SOCKET, SO_REUSEADDR,
-                        reuse_address, NULL);
-  }
-#endif
-
   if (!g_socket_address_to_native (address, &addr, sizeof addr, error))
     return FALSE;
 
+  /* On Windows, SO_REUSEADDR has the semantics we want for UDP
+   * sockets, but has nasty side effects we don't want for TCP
+   * sockets.
+   *
+   * On other platforms, we set SO_REUSEPORT, if it exists, for
+   * UDP sockets, and SO_REUSEADDR for all sockets, hoping that
+   * if SO_REUSEPORT doesn't exist, then SO_REUSEADDR will have
+   * the desired semantics on UDP (as it does on Linux, although
+   * Linux has SO_REUSEPORT too as of 3.9).
+   */
+
+#ifdef G_OS_WIN32
+  so_reuseaddr = reuse_address && (socket->priv->type == G_SOCKET_TYPE_DATAGRAM);
+#else
+  so_reuseaddr = !!reuse_address;
+#endif
+
+#ifdef SO_REUSEPORT
+  so_reuseport = reuse_address && (socket->priv->type == G_SOCKET_TYPE_DATAGRAM);
+#endif
+
+  /* Ignore errors here, the only likely error is "not supported", and
+   * this is a "best effort" thing mainly.
+   */
+  g_socket_set_option (socket, SOL_SOCKET, SO_REUSEADDR, so_reuseaddr, NULL);
+#ifdef SO_REUSEPORT
+  g_socket_set_option (socket, SOL_SOCKET, SO_REUSEPORT, so_reuseport, NULL);
+#endif
+
   if (bind (socket->priv->fd, (struct sockaddr *) &addr,
            g_socket_address_get_native_size (address)) < 0)
     {
diff --git a/gio/tests/socket.c b/gio/tests/socket.c
index 8ae80a1..df1ec70 100644
--- a/gio/tests/socket.c
+++ b/gio/tests/socket.c
@@ -824,6 +824,83 @@ test_unix_connection_ancillary_data (void)
 }
 #endif /* G_OS_UNIX */
 
+static void
+test_reuse_tcp (void)
+{
+  GSocket *sock1, *sock2;
+  GError *error = NULL;
+  GInetAddress *iaddr;
+  GSocketAddress *addr;
+
+  sock1 = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                        G_SOCKET_TYPE_STREAM,
+                        G_SOCKET_PROTOCOL_DEFAULT,
+                        &error);
+  g_assert_no_error (error);
+
+  iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
+  addr = g_inet_socket_address_new (iaddr, 0);
+  g_object_unref (iaddr);
+  g_socket_bind (sock1, addr, TRUE, &error);
+  g_object_unref (addr);
+  g_assert_no_error (error);
+
+  g_socket_listen (sock1, &error);
+  g_assert_no_error (error);
+
+  sock2 = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                        G_SOCKET_TYPE_STREAM,
+                        G_SOCKET_PROTOCOL_DEFAULT,
+                        &error);
+  g_assert_no_error (error);
+
+  addr = g_socket_get_local_address (sock1, &error);
+  g_assert_no_error (error);
+  g_socket_bind (sock2, addr, TRUE, &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE);
+  g_object_unref (addr);
+
+  g_object_unref (sock1);
+  g_object_unref (sock2);
+}
+
+static void
+test_reuse_udp (void)
+{
+  GSocket *sock1, *sock2;
+  GError *error = NULL;
+  GInetAddress *iaddr;
+  GSocketAddress *addr;
+
+  sock1 = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                        G_SOCKET_TYPE_DATAGRAM,
+                        G_SOCKET_PROTOCOL_DEFAULT,
+                        &error);
+  g_assert_no_error (error);
+
+  iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
+  addr = g_inet_socket_address_new (iaddr, 0);
+  g_object_unref (iaddr);
+  g_socket_bind (sock1, addr, TRUE, &error);
+  g_object_unref (addr);
+  g_assert_no_error (error);
+
+  sock2 = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                        G_SOCKET_TYPE_DATAGRAM,
+                        G_SOCKET_PROTOCOL_DEFAULT,
+                        &error);
+  g_assert_no_error (error);
+
+  addr = g_socket_get_local_address (sock1, &error);
+  g_assert_no_error (error);
+  g_socket_bind (sock2, addr, TRUE, &error);
+  g_object_unref (addr);
+  g_assert_no_error (error);
+
+  g_object_unref (sock1);
+  g_object_unref (sock2);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -845,6 +922,8 @@ main (int   argc,
   g_test_add_func ("/socket/unix-connection", test_unix_connection);
   g_test_add_func ("/socket/unix-connection-ancillary-data", test_unix_connection_ancillary_data);
 #endif
+  g_test_add_func ("/socket/reuse/tcp", test_reuse_tcp);
+  g_test_add_func ("/socket/reuse/udp", test_reuse_udp);
 
   return g_test_run();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]