From cb1a6092405b2f6d9b82e7c1b757951d695c5fe6 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Fri, 12 Jun 2009 13:01:04 -0400 Subject: [PATCH] =?utf8?q?Bug=20585575=20=E2=80=93=20g=5Fsocket=5Flistener?= =?utf8?q?=5Fadd=5Finet=5Fport()?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 file 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 * -- 2.7.4