GNOME Bugzilla – Bug 689245
GSocket unable to reuse (address,port) on Mac OS X
Last modified: 2013-08-17 17:32:04 UTC
When reusing an (address,port) on Mac OS X you have to set the SO_REUSEPORT option, otherwise you'll get a "Address is already in use" error.
Created attachment 230137 [details] [review] set SO_REUSEPORT if available
From setsockopt(2): SO_REUSEPORT enables duplicate address and port bindings Even if this might sound semantically different from: SO_REUSEADDR enables local address reuse ...it's actually what Linux combines implicitly with SO_REUSEADDR.. so the semantics of g_socket_bind would not change. https://developer.apple.com/library/ios/#documentation/system/conceptual/manpages_iphoneos/man2/setsockopt.2.html
From what I can find, non-BSD SO_REUSEADDR implies the semantics of BSD SO_REUSEPORT only for multicast sockets. So the patch needs to check if @address is a GInetSocketAddress, and if so, call g_inet_address_get_is_multicast() on its GInetAddress, and only set SO_REUSEPORT in that case. If you aren't using multicast, then the behavior you're getting is intentional; only one process can bind a given port at a time.
Multicast is indeed the usecase, and I didn't think of what you said. Thanks for pointing it out. I'll see if I can update the patch.
Thought about it a bit more, since you may want to send a UDP multicast message by binding (0.0.0.0, dest_port), and then send_to((multicast-address, dest-port), buffer). Then IS_MULTICAST wouldn't return true and thus the check you suggest would not work. And as I said, this only applies to Mac OS X, as on Linux, simply using REUSEADDR does what REUSEADDR + REUSEPORT does combined on Mac OS X if I understand correctly.
...unless ofc it ought to be is_multicast _and_ is_any? Sorry, I'm very new to this, just trying to get my code to work on both OS X and Linux. Guidance very much appreciated.
To clear out some possible question marks, here's the code (vala) that fails on Mac OS X without the attached patch: http://04f33c24cc186275.paste.se/ interesting here is the start() method.
(In reply to comment #5) > Thought about it a bit more, since you may want to send a UDP multicast message > by binding (0.0.0.0, dest_port), and then send_to((multicast-address, > dest-port), buffer). binding 0.0.0.0 doesn't make sense for a client-side port though... ? Everything I can find suggests that SO_REUSEPORT is really only intended for server sockets. Eg, from the OS X setsockopt() man page: SO_REUSEPORT allows completely duplicate bindings by multiple processes if they all set SO_REUSEPORT before binding the port. This option permits multiple instances of a program to each receive UDP/IP multicast or broadcast datagrams destined for the bound port. > And as I said, this only applies to Mac OS X, as on > Linux, simply using REUSEADDR does what REUSEADDR + REUSEPORT does combined on > Mac OS X if I understand correctly. The problem is that setting SO_REUSEPORT on a non-multicast socket when the caller isn't expecting it is a security hole and/or bug. It would let you have two copies of a server program running at the same time, both listening on the same port, each unaware of the other one.
Binding the client socket to 0.0.0.0 is pretty useless yes, the important part is to cause the UDP messages sent from the client to have the correct origin port. Otherwise the message will be ignored by the other parties.
As GSocket exposes its file descriptor the workaround is trivial, if someone else stumbles upon this bug this is how I decided to solve it: https://github.com/dsvensson/subzero/commit/30cccfbb890152fa6ceb0fd48c49ca9101c30377
OK. Here's what I've been able to determine from google and experimentation. - SO_REUSEPORT allows multiple sockets to all bind() to the same address at the same time, as long as all of them specify SO_REUSEPORT. This is intended for receiving broadcast and multicast packets in multiple processes; the semantics for unicast packets are not well defined. - on BSD, SO_REUSEADDR on a TCP socket has only the traditional "unbreak my server" behavior (allowing you to re-grab a listening socket after a server crashes without having to wait for all of its old TCP connections to time out first). In theory, SO_REUSEADDR on a multicast UDP socket (which I guess means one that you have already called g_socket_join_multicast_group() on) behaves as SO_REUSEPORT, but I could not reproduce this on OS X. AFAICT, it's just a no-op. - On Linux, SO_REUSEADDR on a TCP socket is mostly the same as BSD SO_REUSEADDR, and SO_REUSEADDR on a UDP socket is mostly the same as BSD SO_REUSEPORT. - On Windows... things are messed up. SO_REUSEADDR is more like SO_REUSEPORT, but with poorly-designed access controls. Which is why we don't use it in g_socket_bind() currently. So anyway, the end result of this is that if you call g_socket_bind() with a UDP socket, and pass TRUE for @allow_reuse, then you get a "reuseport"-type socket on Linux, but a non-"reuseport"-type socket on BSD and Windows. Unless you added the socket to a multicast group first, in which case you *might* get reuseport semantics on BSD too. Given that the Linux semantics are useful, and that they're what most glib users are already getting, contrary to the docs, I think we should just standardize on that, and fix the docs and the code to do it on all platforms.
Created attachment 236490 [details] [review] 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.
pushed a slightly different patch. FTR, since comment 11 was written, Linux has also gotten SO_REUSEPORT support, though it only affects things we don't guarantee the semantics of anyway (unicast packets to multiple UDP listeners).
Created attachment 252060 [details] test program The program I used to test the behavior of SO_REUSEADDR and SO_REUSEPORT on different platforms. Stashing it here for possible future reference.