classification
Title: Add socket.bind_socket() convenience function
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.8
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: giampaolo.rodola Nosy List: Carlos.Ralli, Paul Marks, andreasr, asvetlov, berker.peksag, cheryl.sabella, dazhaoyu, giampaolo.rodola, gregory.p.smith, jaraco, jleedev, josiah.carlson, jpokorny, loewis, martin.panter, neologix, nirs, pitrou, r.david.murray, vstinner
Priority: normal Keywords: needs review, patch

Created on 2013-03-27 16:48 by giampaolo.rodola, last changed 2019-04-09 17:53 by vstinner. This issue is now closed.

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
Pull Requests
URL Status Linked Edit
PR 11215 closed giampaolo.rodola, 2018-12-18 19:35
PR 11784 giampaolo.rodola, 2019-02-12 04:42
PR 12735 merged giampaolo.rodola, 2019-04-09 01:38
Messages (29)
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 committer) 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!
msg331943 - (view) Author: Jason R. Coombs (jaraco) * (Python committer) Date: 2018-12-17 01:42
I do believe this issue is still important and relevant. See issue25667 for a duplicate ticket (and references to implementations) and issue24209 for another issue where this could have been applied.
msg331944 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2018-12-17 02:47
Interesting. Yes, I agree this proposal is still desirable. We can reuse socket.create_server_sock() in smtpd, ftplib, socketserver (issue20215) and http.server (issue24209) modules which will inherit dual-stack IPv4/6 capabilities for free. I should be able to provide a PR sometime during this month.
msg335021 - (view) Author: Jason R. Coombs (jaraco) * (Python committer) Date: 2019-02-07 13:32
In issue24209, I ended up settling on this implementation (https://github.com/python/cpython/blob/f289084c83190cc72db4a70c58f007ec62e75247/Lib/http/server.py#L1227-L1234), which seems to work well.
msg335036 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2019-02-07 19:24
After careful thinking I realize I'm not completely sure about how to expose the IPv4/6 functionality yet. I prefer to defer it for later and start landing a bind_socket() utility function which serves as a base for this functionality. See: issue17561.
msg335285 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2019-02-12 04:42
After iterating over this over the last few days I realized it makes more sense to implement and discuss the whole thing in here and as a single PR, so sorry about previously splitting this in a separate ticket/PR. Relevant PR is now this one and is ready for review:
https://github.com/python/cpython/pull/11784
Relevant changes which probably require attention/discussion are: 

1) since it was sort of ambiguous I renamed "has_dual_stack()" to "supports_hybrid_ipv46()". There could be space for some bikeshed as we already have "socket.has_ipv6" so this may be "has_hybrid_ipv46()" called instead

2) if family is unspecified and determined from *host* (e.g. "localhost" or "") the function sorts getaddrinfo() results preferring AF_INET over AF_INET6

3) doc includes link to my http://code.activestate.com/recipes/578504 recipe for platforms not supporting hybrid_ipv46 natively

4) it may be worthwhile (or maybe not?) to have another complementary bind_sockets() (plural) function returning all items from getaddrinfo(). That would be useful for non-blocking apps/frameworks and could be reused by asyncio.

Also, I'm CC-ing people from issue20215 as it contains relevant comments.
msg339678 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2019-04-08 22:39
Patch committed as of:
https://github.com/python/cpython/commit/eb7e29f2a9d075accc1ab3faf3612ac44f5e2183
For posterity, since the API evolved since the original proposal (as per PR review/suggestions), this is the final incarnation:

    # IPv4 only
    >>> socket.create_server(addr)  
    # IPv6 only
    >>> socket.create_server(addr, family=socket.AF_INET6)
    # IPv4 + IPv6
    >>> socket.create_server(addr, family=socket.AF_INET6, dualstack_ipv6=True)
    # IPv4/6 if possible and don't care about IPv4 mapped addresses, else IPv4
    >>> if socket.has_dualstack_ipv6():
    ...    s = socket.create_server(addr, family=socket.AF_INET6, dualstack_ipv6=True)
    ... else:
    ...    s = socket.create_server(addr)
msg339687 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2019-04-09 00:19
The change broke multiple buildbots. Example:

https://buildbot.python.org/all/#builders/16/builds/2625

Traceback (most recent call last):
  File "/home/dje/cpython-buildarea/3.x.edelsohn-sles-z/build/Lib/test/_test_multiprocessing.py", line 4377, in test_wait_socket
    self.assertEqual(b''.join(v), expected)
AssertionError: b'1\n2\n3\n4\n5\n6\n7\n8\n9\n' != b'0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n'

More logs:

https://github.com/python/cpython/pull/11784#issuecomment-481036369
msg339694 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2019-04-09 02:42
New changeset 8702b67dad62a9084f6c1823dce10653743667c8 by Giampaolo Rodola in branch 'master':
BPO-17561: set create_server backlog default to None (GH-12735)
https://github.com/python/cpython/commit/8702b67dad62a9084f6c1823dce10653743667c8
msg339723 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2019-04-09 10:34
Fixed. There's a remaining failing BB:
https://buildbot.python.org/all/#/builders/176/builds/185/steps/4/logs/stdio
...but the failure appears unrelated and it has been red for a while.
msg339794 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2019-04-09 17:53
"""
Fixed. There's a remaining failing BB:
https://buildbot.python.org/all/#/builders/176/builds/185/steps/4/logs/stdio
...but the failure appears unrelated and it has been red for a while.
"""

That's known and unrelated issue: https://bugs.python.org/issue31453
History
Date User Action Args
2019-04-09 17:53:09vstinnersetmessages: + msg339794
2019-04-09 10:34:52giampaolo.rodolasetstatus: open -> closed
resolution: fixed
messages: + msg339723

stage: patch review -> resolved
2019-04-09 02:42:17giampaolo.rodolasetmessages: + msg339694
2019-04-09 01:38:02giampaolo.rodolasetstage: resolved -> patch review
pull_requests: + pull_request12658
2019-04-09 00:19:24vstinnersetstatus: closed -> open

nosy: + vstinner
messages: + msg339687

resolution: fixed -> (no value)
2019-04-08 22:39:30giampaolo.rodolasetmessages: + msg339678
2019-04-08 22:35:18giampaolo.rodolasetstatus: open -> closed
resolution: fixed
stage: commit review -> resolved
2019-02-19 10:44:58giampaolo.rodolalinkissue29515 dependencies
2019-02-17 19:02:34giampaolo.rodolasetstage: patch review -> commit review
2019-02-12 04:42:06giampaolo.rodolasetnosy: + gregory.p.smith, nirs, r.david.murray, berker.peksag, martin.panter, jpokorny, jleedev, dazhaoyu, andreasr, Carlos.Ralli, Paul Marks
title: Add socket.create_server_sock() convenience function -> Add socket.bind_socket() convenience function
messages: + msg335285
pull_requests: + pull_request11856

keywords: - easy
2019-02-07 19:24:06giampaolo.rodolasetmessages: + msg335036
2019-02-07 13:32:51jaracosetmessages: + msg335021
2018-12-18 20:43:22vstinnersetnosy: - vstinner
2018-12-18 19:53:53gvanrossumsetnosy: - gvanrossum
2018-12-18 19:35:20giampaolo.rodolasetstage: patch review
pull_requests: + pull_request10452
2018-12-17 02:47:20giampaolo.rodolasetassignee: giampaolo.rodola
messages: + msg331944
2018-12-17 01:42:28jaracosetnosy: + jaraco

messages: + msg331943
versions: + Python 3.8, - Python 3.4
2018-12-17 01:39:39jaracolinkissue25667 superseder
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