From 547df5937cc3b821498e27eb55cebc1f414ce597 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Sun, 17 Feb 2013 15:11:18 -0500 Subject: [PATCH] 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 8d13e1217..a1b00a347 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 SO_REUSEADDR 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 8ae80a15d..df1ec7099 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(); }