classification
Title: Add socket.create_server_sock() convenience function
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, cheryl.sabella, giampaolo.rodola, gvanrossum, josiah.carlson, loewis, neologix, pitrou, vstinner
Priority: normal Keywords: easy, needs review, patch

Created on 2013-03-27 16:48 by giampaolo.rodola, last changed 2018-10-14 18:49 by cheryl.sabella.

Files
File name Uploaded Description Edit
socket.patch giampaolo.rodola, 2013-03-27 16:48 adds create_server_sock() review
socket2.patch giampaolo.rodola, 2013-03-28 16:34 disable IPV6_V6ONLY, adds has_dual_stack() review
Messages (19)
msg185348 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-27 16:48
Here's a function similar to socket.create_connection() which addresses all the repetitive tasks needed to order to create an IPv4/IPv6 agnostic server socket.
msg185362 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2013-03-27 18:39
I think that's a good idea.

However, there's a problem with the implementation: if one passes "" or None as address on a dual-stack node, the resulting socket will be either IPv4 bound to INADDR_ANY, or IPv6 bound to IN6ADDR_ANY, whereas one would expect to be bound both in IPv4 and IPv6. In the later case, some platforms (like Linux) use IPv4-mapped addresses by default, some others (e.g. some versions of FreeBSD) don't. So, depending on IPV6_V6ONLY setting, binding to IPV6 any won't accept IPv4 connections. Also, some platforms don't support mapped addresses at all, so the only portable solution is to bind both to 0.0.0.0 and ::, whith two different sockets, and use select() to accept both.

So it would maybe make sense to expose a ServerSocket class, with an accept method which would do the right thing (as an added bonus, it could expose set_reuse_addr(bool), since SO_REUSEADDR have subtle semantic differences between Unix and Windows).
msg185369 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-27 19:50
What you say is right but whether the kernel supports an hybrid IPv4/6 stack or not there's not much we can do about it anyway.
Exactly what are you suggesting with the ServerSocket class you mentioned? What do you expect it to do?
Note that platforms supporting the dual-stack are already supported. This:

>>> socket.create_server_socket(("::", 0))

...on Linux will create a socket which is reachable both as "::1" and "127.0.0.1".
So if I'm not mistaken the alias for specifying "listen on all interfaces for both IPv4 and IPv6 addresses" would be "::" instead of "" / None.
We can document that.
msg185370 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-27 20:01
Side note: this is how in pyftpdlib I determine whether a platform supports the dual stack:

def support_hybrid_ip_v4_v6():
    # Note: IPPROTO_IPV6 constant is broken on Windows, see:
    # http://bugs.python.org/issue6926
    sock = None
    try:
        if not socket.has_ipv6:
            return False
        sock = socket.socket(socket.AF_INET6)
        return not sock.getsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY)
    except (socket.error, AttributeError):
        return False
    finally:
        if sock is not None:
            sock.close()
msg185372 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-03-27 20:06
Tulip has something similar.  Someone should compare the two and make sure they are equivalent or similar.
msg185374 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2013-03-27 20:32
> What you say is right but whether the kernel supports an hybrid IPv4/6 stack
> or not there's not much we can do about it anyway.
> Exactly what are you suggesting with the ServerSocket class you mentioned?
> What do you expect it to do?

There's a confusion.
Dual-stack merely means that the host supports both IPv4 and IPv6 natively.
The IPV6_V6ONLY tunable is just a way to enable or not IPv4-mapped
addresses (i.e. ::ffff::<IPv4 address>) so that an IPv4 client can
connect to this socket. It can be set system-wide, or on a socket
basis.

> Note that platforms supporting the dual-stack are already supported. This:
>
>>>> socket.create_server_socket(("::", 0))
>
> ...on Linux will create a socket which is reachable both as "::1" and
> "127.0.0.1".

Try the same thing after:
# echo 1 > /proc/sys/net/ipv6/bindv6only

It won't accept IPv4 connections anymore.

And that's the default on many (most) platforms, e.g. FreeBSD and
OpenBSD (I think it's also true for Windows).

So the bottom line is that with the current code, on some - most -
platforms, we'll only accept IPv6 connections (and since you iterate
over getaddrinfo() in an unspecified order you may very well bind to
IPv4 only first, in which case you'll only accept IPv4 clients).

The proper way to procedd, on platforms which don't support unsetting
IPV6_V6ONLY, is to use two sockets, one in IPv4, and one IPv6, and use
select() to accept connections.

This would propably belong to an overriden accept() method in a
ServerSocket class, since it's far from trivial.
msg185443 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-28 12:53
Thanks for clarifying, I have a better understanding of the problem now.
Providing a custom class instantiating two sockets looks like a dead end to me though. To say one, what is getsockname() supposed to return? Same for detach(), fileno(), 'family' and probably others I can't think of right now.
msg185447 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2013-03-28 13:32
> Providing a custom class instantiating two sockets looks like a dead end to me though. To say one, what is getsockname() supposed to return? Same for detach(), fileno(), 'family' and probably others I can't think of right now.

Indeed.

So we might opt for a best-effort approach: try disabling IPV6_V6ONLY
on the socket if it's set: that way it should work on most platforms.
And add a note to the documentation. IIRC that's what Java does.
msg185456 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-28 16:34
Agreed. Then it probably makes sense to expose also a socket.has_dual_stack() function so that the user can pre-emptively decide whether using a custom class.
Updated draft patch is in attachment.
msg185483 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-29 02:14
I managed to write a container class which listens on multiples addresses and uses select/poll on accept() as suggested by Charles.
FWICT it seems to work pretty well and supports non-blocking sockets and timeouts (tested on Linux, Windows and OSX).

It is available as a recipe here:
http://code.activestate.com/recipes/578504

IMO has_dual_stack() and create_server_sock() functions can already be included as-is (will adapt the recipe in order to get rid of Python 2.X support and submit another patch).

As for including also MultipleSocketsListener that's debatable.
It can either be added or linked/mentioned it in the doc.
msg185486 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-03-29 02:23
Perhaps you can contribute something like this to Tulip?  We've got code to run a server that can handle IPv4 and IPv6, but we currently don't have something that just creates a separate socket for each address family.  Our UDP and TCP paths are also quite different.
msg185487 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-29 02:51
Yep, no prob. It would also be a good chance to test it in a real-world app. Where should I look?
msg185489 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-03-29 03:51
Tulip is at code.google.com/p/tulip

On Thu, Mar 28, 2013 at 7:51 PM, Giampaolo Rodola'
<report@bugs.python.org>wrote:

>
> Giampaolo Rodola' added the comment:
>
> Yep, no prob. It would also be a good chance to test it in a real-world
> app. Where should I look?
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17561>
> _______________________________________
>
msg185497 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-03-29 11:58
>> Where should I look?
> Tulip is at code.google.com/p/tulip

I meant in the code (and what needs to be done/refactored exactly).
msg185499 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-03-29 15:37
start_serving() in base_events.py.

On Fri, Mar 29, 2013 at 4:58 AM, Giampaolo Rodola'
<report@bugs.python.org>wrote:

>
> Giampaolo Rodola' added the comment:
>
> >> Where should I look?
> > Tulip is at code.google.com/p/tulip
>
> I meant in the code (and what needs to be done/refactored exactly).
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17561>
> _______________________________________
>
msg185765 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-04-01 20:13
Being Tulip asynchronous I think that what it needs is an utility function which returns *multiple* sockets as are the addresses returned by getaddrinfo() and also possibly even disable the IPv4/6 dual stack in order to be consistent across all platforms.

After the sockets are returned they can be "registered" against the event loop as two separate entities such as, say, ("0.0.0.0", 8000) *and* ("::", 8000).
If you think this makes sense I can contribute something like this into Tulip, or I can bring it up on Tulip's ml and ask for other people's opinions.

My current recipe is different in that it provides a function which bind()s on one socket only and tries to enable the dual stack whenever possible in order to support IPv4 and IPv6 with a single socket.
In this it is similar to socket.create_connection() and clearly favors blocking socket usages (although it can also be used in non-blocking apps) which kind of represents the default for the stdlib.
msg185849 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-04-02 17:02
Nikolay, you may want to check out this Python tracker issue.

Giampaolo: I didn't even know it was possible for a single socket to be
dual-stack. (It would seem problematic for UDP recvfrom() for example.) In
Tulip it does indeed make more sense to create separate sockets and just
listen on all of them (using the same protocol factory). This can be
completely transparent to the code calling start_serving() and to the
protocol implementation. So if you're brave you can just send a code review
using codereview.appspot.com to the python-tulip list!

On Mon, Apr 1, 2013 at 1:13 PM, Giampaolo Rodola' <report@bugs.python.org>wrote:

>
> Giampaolo Rodola' added the comment:
>
> Being Tulip asynchronous I think that what it needs is an utility function
> which returns *multiple* sockets as are the addresses returned by
> getaddrinfo() and also possibly even disable the IPv4/6 dual stack in order
> to be consistent across all platforms.
>
> After the sockets are returned they can be "registered" against the event
> loop as two separate entities such as, say, ("0.0.0.0", 8000) *and* ("::",
> 8000).
> If you think this makes sense I can contribute something like this into
> Tulip, or I can bring it up on Tulip's ml and ask for other people's
> opinions.
>
> My current recipe is different in that it provides a function which
> bind()s on one socket only and tries to enable the dual stack whenever
> possible in order to support IPv4 and IPv6 with a single socket.
> In this it is similar to socket.create_connection() and clearly favors
> blocking socket usages (although it can also be used in non-blocking apps)
> which kind of represents the default for the stdlib.
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17561>
> _______________________________________
>
msg185919 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2013-04-03 12:46
Here's a patch for Tulip:
https://codereview.appspot.com/8307045
Will ping Tulip's ml as well.
msg327719 - (view) Author: Cheryl Sabella (cheryl.sabella) * (Python triager) Date: 2018-10-14 18:49
Since Tulip/asyncio has gone through a lot of development since this issue was added, I wasn't sure if this has been included already or if there was still interest in it.  In either case, I think it might be able to be closed, but I wanted to make sure first.  Thanks!
History
Date User Action Args
2018-10-14 18:49:27cheryl.sabellasetnosy: + cheryl.sabella, asvetlov
messages: + msg327719
2013-04-03 12:46:24giampaolo.rodolasetmessages: + msg185919
2013-04-03 01:55:14vstinnersetnosy: + vstinner
2013-04-02 17:02:57gvanrossumsetmessages: + msg185849
2013-04-01 20:13:26giampaolo.rodolasetmessages: + msg185765
2013-03-29 15:37:17gvanrossumsetmessages: + msg185499
2013-03-29 11:58:56giampaolo.rodolasetmessages: + msg185497
2013-03-29 03:51:10gvanrossumsetmessages: + msg185489
2013-03-29 03:35:20giampaolo.rodolasetnosy: + loewis, josiah.carlson
2013-03-29 02:51:52giampaolo.rodolasetmessages: + msg185487
2013-03-29 02:23:20gvanrossumsetmessages: + msg185486
2013-03-29 02:14:25giampaolo.rodolasetmessages: + msg185483
2013-03-28 16:34:40giampaolo.rodolasetfiles: + socket2.patch

messages: + msg185456
2013-03-28 13:32:52neologixsetmessages: + msg185447
2013-03-28 12:53:18giampaolo.rodolasetmessages: + msg185443
2013-03-27 20:32:17neologixsetmessages: + msg185374
2013-03-27 20:06:06gvanrossumsetmessages: + msg185372
2013-03-27 20:01:09giampaolo.rodolasetmessages: + msg185370
2013-03-27 19:50:10giampaolo.rodolasetmessages: + msg185369
2013-03-27 18:39:41neologixsetnosy: + neologix
messages: + msg185362
2013-03-27 17:07:14pitrousetnosy: + gvanrossum
2013-03-27 16:48:29giampaolo.rodolacreate