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
This commit is contained in:
Dan Winship 2013-02-17 15:11:18 -05:00
parent 2ea4af6f01
commit 547df5937c
2 changed files with 126 additions and 20 deletions

View File

@ -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)
{

View File

@ -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();
}