[glib] Bug 585575 – g_socket_listener_add_inet_port()



commit cb1a6092405b2f6d9b82e7c1b757951d695c5fe6
Author: Ryan Lortie <desrt desrt ca>
Date:   Fri Jun 12 13:01:04 2009 -0400

    Bug 585575 â?? g_socket_listener_add_inet_port()
    
    Change the logic in g_socket_listener_add_inet_port() as per the
    reasoning in the bug report.
    
      - If the OS supports neither IPv6 or IPv4, fail.
      - If the OS supports only IPv6, do that.
      - If the OS supports only IPv4, do that.
      - If the OS supports IPv6 and IPv6 "speaks" IPv4 then bind it
        and be done.
      - If the OS supports IPv6 and IPv6 doesn't "speak" IPv4 then
        create an additional socket for IPv4.
      - If binding any socket fails then fail the entire call.
    
    Also, remove the ability to call this function with port == 0.  This
    is a useless thing to do anyway since you have no way to know what
    port number was actually allocated.  We should have a separate
    function to deal with this.

 gio/gsocketlistener.c |  164 +++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 126 insertions(+), 38 deletions(-)
---
diff --git a/gio/gsocketlistener.c b/gio/gsocketlistener.c
index c675ae8..ca96530 100644
--- a/gio/gsocketlistener.c
+++ b/gio/gsocketlistener.c
@@ -256,6 +256,11 @@ g_socket_listener_add_socket (GSocketListener  *listener,
  * it to @address and adds it to the set of sockets we're accepting
  * sockets from.
  *
+ * Note that adding an IPv6 address, depending on the platform,
+ * may or may not result in a listener that also accepts IPv4
+ * connections.  For more determinstic behaviour, see
+ * g_socket_listener_add_inet_port().
+ *
  * @source_object will be passed out in the various calls
  * to accept to identify this particular source, which is
  * useful if you're listening on multiple addresses and do
@@ -305,7 +310,7 @@ g_socket_listener_add_address (GSocketListener  *listener,
 /**
  * g_socket_listener_add_inet_port:
  * @listener: a #GSocketListener
- * @port: an ip port number
+ * @port: an IP port number (non-zero)
  * @source_object: Optional #GObject identifying this source
  * @error: #GError for error reporting, or %NULL to ignore.
  *
@@ -328,54 +333,137 @@ g_socket_listener_add_inet_port (GSocketListener  *listener,
 				 GObject          *source_object,
 				 GError          **error)
 {
-  GSocketAddress *address4, *address6;
-  GInetAddress *inet_address;
-  gboolean res;
+  gboolean need_ipv4_socket = TRUE;
+  GSocket *socket4 = NULL;
+  GSocket *socket6;
+
+  g_return_val_if_fail (listener != NULL, FALSE);
+  g_return_val_if_fail (port != 0, FALSE);
 
   if (!check_listener (listener, error))
     return FALSE;
 
-  inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
-  address4 = g_inet_socket_address_new (inet_address, port);
-  g_object_unref (inet_address);
+  /* first try to create an IPv6 socket */
+  socket6 = g_socket_new (G_SOCKET_FAMILY_IPV6,
+                          G_SOCKET_TYPE_STREAM,
+                          G_SOCKET_PROTOCOL_DEFAULT,
+                          NULL);
+
+  if (socket6 != NULL)
+    /* IPv6 is supported on this platform, so if we fail now it is
+     * a result of being unable to bind to our port.  Don't fail
+     * silently as a result of this!
+     */
+    {
+      GInetAddress *inet_address;
+      GSocketAddress *address;
+      gboolean result;
 
-  inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
-  address6 = g_inet_socket_address_new (inet_address, port);
-  g_object_unref (inet_address);
+      inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
+      address = g_inet_socket_address_new (inet_address, port);
+      g_object_unref (inet_address);
 
-  if (!g_socket_listener_add_address (listener,
-				      address6,
-				      G_SOCKET_TYPE_STREAM,
-				      G_SOCKET_PROTOCOL_DEFAULT,
-				      source_object,
-				      NULL))
-    {
-      /* Failed, to create ipv6, socket, just use ipv4,
-	 return any error */
-      res = g_socket_listener_add_address (listener,
-					   address4,
-					   G_SOCKET_TYPE_STREAM,
-					   G_SOCKET_PROTOCOL_DEFAULT,
-					   source_object,
-					   error);
+      g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog);
+
+      result = g_socket_bind (socket6, address, TRUE, error) &&
+               g_socket_listen (socket6, error);
+
+      g_object_unref (address);
+
+      if (!result)
+        {
+          g_object_unref (socket6);
+
+          return FALSE;
+        }
+
+      if (source_object)
+        g_object_set_qdata_full (G_OBJECT (socket6), source_quark,
+                                 g_object_ref (source_object),
+                                 g_object_unref);
+
+      /* If this socket already speaks IPv4 then we are done. */
+      if (g_socket_speaks_ipv4 (socket6))
+        need_ipv4_socket = FALSE;
     }
-  else
+
+  if (need_ipv4_socket)
+    /* We are here for exactly one of the following reasons:
+     *
+     *   - our platform doesn't support IPv6
+     *   - we successfully created an IPv6 socket but it's V6ONLY
+     *
+     * In either case, we need to go ahead and create an IPv4 socket
+     * and fail the call if we can't bind to it.
+     */
     {
-      /* Succeeded with ipv6, also try ipv4 in case its ipv6 only,
-	 but ignore errors here */
-      res = TRUE;
-      g_socket_listener_add_address (listener,
-				     address4,
-				     G_SOCKET_TYPE_STREAM,
-				     G_SOCKET_PROTOCOL_DEFAULT,
-				     source_object,
-				     NULL);
+      socket4 = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                              G_SOCKET_TYPE_STREAM,
+                              G_SOCKET_PROTOCOL_DEFAULT,
+                              error);
+
+      if (socket4 != NULL)
+        /* IPv4 is supported on this platform, so if we fail now it is
+         * a result of being unable to bind to our port.  Don't fail
+         * silently as a result of this!
+         */
+        {
+          GInetAddress *inet_address;
+          GSocketAddress *address;
+          gboolean result;
+
+          inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
+          address = g_inet_socket_address_new (inet_address, port);
+          g_object_unref (inet_address);
+
+          g_socket_set_listen_backlog (socket4,
+                                       listener->priv->listen_backlog);
+
+          result = g_socket_bind (socket4, address, TRUE, error) &&
+                   g_socket_listen (socket4, error);
+
+          g_object_unref (address);
+
+          if (!result)
+            {
+              g_object_unref (socket4);
+
+              if (socket6 != NULL)
+                g_object_unref (socket6);
+
+              return FALSE;
+            }
+
+          if (source_object)
+            g_object_set_qdata_full (G_OBJECT (socket4), source_quark,
+                                     g_object_ref (source_object),
+                                     g_object_unref);
+        }
+      else
+        /* Ok.  So IPv4 is not supported on this platform.  If we
+         * succeeded at creating an IPv6 socket then that's OK, but
+         * otherwise we need to tell the user we failed.
+         */
+        {
+          if (socket6 != NULL)
+            g_clear_error (error);
+          else
+            return FALSE;
+        }
     }
 
-  g_object_unref (address4);
-  g_object_unref (address6);
+  g_assert (socket6 != NULL || socket4 != NULL);
+
+  if (socket6 != NULL)
+    g_ptr_array_add (listener->priv->sockets, socket6);
+
+  if (socket4 != NULL)
+    g_ptr_array_add (listener->priv->sockets, socket4);
 
-  return res;
+  if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
+    G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
+
+  return TRUE;
 }
 
 static GList *



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