After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 689245 - GSocket unable to reuse (address,port) on Mac OS X
GSocket unable to reuse (address,port) on Mac OS X
Status: RESOLVED FIXED
Product: glib
Classification: Platform
Component: network
2.35.x
Other Mac OS
: Normal normal
: ---
Assigned To: gtkdev
gtkdev
Depends on:
Blocks:
 
 
Reported: 2012-11-28 23:29 UTC by Daniel Svensson
Modified: 2013-08-17 17:32 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
set SO_REUSEPORT if available (1.04 KB, patch)
2012-11-28 23:35 UTC, Daniel Svensson
needs-work Details | Review
GSocket: fix g_socket_bind() allow_reuse semantics (7.40 KB, patch)
2013-02-17 20:40 UTC, Dan Winship
committed Details | Review
test program (5.43 KB, text/plain)
2013-08-17 17:32 UTC, Dan Winship
  Details

Description Daniel Svensson 2012-11-28 23:29:31 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.
Comment 1 Daniel Svensson 2012-11-28 23:35:26 UTC
Created attachment 230137 [details] [review]
set SO_REUSEPORT if available
Comment 2 Daniel Svensson 2012-11-29 07:25:44 UTC
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
Comment 3 Dan Winship 2012-11-29 17:36:49 UTC
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.
Comment 4 Daniel Svensson 2012-11-29 21:22:14 UTC
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.
Comment 5 Daniel Svensson 2012-12-01 22:25:35 UTC
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.
Comment 6 Daniel Svensson 2012-12-01 22:28:17 UTC
...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.
Comment 7 Daniel Svensson 2012-12-02 09:20:48 UTC
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.
Comment 8 Dan Winship 2012-12-05 14:57:00 UTC
(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.
Comment 9 Daniel Svensson 2012-12-05 19:48:10 UTC
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.
Comment 10 Daniel Svensson 2012-12-12 22:17:15 UTC
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
Comment 11 Dan Winship 2013-02-17 20:40:12 UTC
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.
Comment 12 Dan Winship 2013-02-17 20:40:32 UTC
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.
Comment 13 Dan Winship 2013-08-17 17:30:11 UTC
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).
Comment 14 Dan Winship 2013-08-17 17:32:04 UTC
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.