classification
Title: socketserver.TCPServer can not listen IPv6 address
Type: enhancement Stage: patch review
Components: Library (Lib) Versions: Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Carlos.Ralli, Paul Marks, andreasr, berker.peksag, dazhaoyu, giampaolo.rodola, gregory.p.smith, jaraco, jleedev, jpokorny, martin.panter, neologix, nirs, r.david.murray
Priority: normal Keywords: patch

Created on 2014-01-10 02:03 by dazhaoyu, last changed 2019-02-18 10:50 by giampaolo.rodola.

Files
File name Uploaded Description Edit
issue20215_socketserver.patch andreasr, 2014-05-03 22:02 Patch against latest Python 3.5 mercurial branch review
issue20215-gps01.diff gregory.p.smith, 2016-08-02 23:23 review
Messages (14)
msg207818 - (view) Author: dzyu (dazhaoyu) Date: 2014-01-10 02:03
I see, in python 2.7, in SocketServer.py the TCPServer implementation is hard-coded to use ipv4, can not handle IPv6 case. it hard-coded set address_family as socket.AF_INET. so when binding IPv6 host, it will throw "gaierror: [Errno -9] Address family for hostname not supported".

The code should to judge the provided host is IPv4 or IPv6, and base on the host type to set address_family as socket.AF_INET or  socket.AF_INET6
msg207824 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-01-10 02:22
The *default* is AF_INET.  If you are requesting a more convenient API than subclassing (which for IPV6 support I think would be a reasonable request), that would be a new feature.
msg217833 - (view) Author: Andreas Røsdal (andreasr) * Date: 2014-05-03 22:02
Here's a patch which implements support in SocketServer to set IPv6 socket address family to AF_INET6 automatically if the TCPServer is specified to listen to an IPv6 address. This means that users of the TCPServer class will get the correct address family automatically for both IPv4 and IPv6 based on the IP address they specify, and hopefully this will be more userfriendly. The patch is against the latest Python 3.5.

Note that this is my first patch to the Python project, I hope I did everything correct.
msg226219 - (view) Author: Andreas Røsdal (andreasr) * Date: 2014-09-01 08:48
Is there any interest in this patch? it would be nice with a review of the patch.  :)
msg231363 - (view) Author: Carlos Ralli (Carlos.Ralli) Date: 2014-11-19 09:47
Is it possible to use this patch for python2.7 ?
msg231377 - (view) Author: Carlos Ralli (Carlos.Ralli) Date: 2014-11-19 12:45
Sorry, I've got nothing. Did you forget the attachment or mail body ?

2014-11-19 11:08 GMT+01:00 Berker Peksag <report@bugs.python.org>:

>
> Changes by Berker Peksag <berker.peksag@gmail.com>:
>
>
> ----------
> nosy: +berker.peksag
> stage:  -> patch review
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue20215>
> _______________________________________
>
msg231379 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-11-19 14:29
Andreas: I would prefer to avoid a dependency on the ipaddress module.  I would suggest adding an address_family constructor argument that defaults to None, where a value of None would mean "just pass the server_address to bind (or getaddrinfo?) and find out what the resulting family is".  I'm not exactly a socket programming expert, though, so there may be a reason I'm not aware of that that won't work well.

Carlos: no, this is a new feature, so it can go into 3.5 only at this point.
msg243884 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-05-23 04:01
I’m no IPv6 expert, but see Lib/smtpd.py:657 and Issue 14758 for how this sort of thing was done for the SMTP server using getaddrinfo(). I think it is similar to David’s suggestion.

I also left a comment about the documentation. And it probably needs a test case as well.
msg271865 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2016-08-02 23:23
TL;DR - We really need reliable tests for the exact behavior we want before coming up with patches.

attached is a patch (-gps01) that would do the same thing as Lib/smtpd.py does... But I'm not convinced it is a good idea.

Would forcing a socket.getaddrinfo() call from the constructor cause problems?  This would be new behavior over what TCPServer did in the past.  Could it trigger a blocking reverse DNS lookup where there wasn't one in the past?

What about when server_address[0] is ''?  getaddrinfo() fails on that, but the existing code works and binds to 0.0.0.0.  Presumably this should bind via AF_INET6 if the host supports it but a simple getaddrinfo() call doesn't tell us that.
msg271869 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-08-03 02:07
I also have reservations about using getaddrinfo() like this. Some potential problems:

1. IPv4-only compatibility: On my Linux computer, getaddrinfo("localhost", 0) returns AF_INET6 before AF_INET. Some programs, like Firefox and BSD netcat, will try both v4 and v6 and probably succeed (netcat first warned about “connection refused” for IPv4). However, other programs only support IPv4 at all or by default. Socat supports v6 but requires an explicit TCP6 or pf=ip6 argument. Gnu netcat only seems to support IPv4. This could also have been a problem with smtpd, or maybe SMTP clients are supposed to be smarter and it is okay (I am not familiar with the protocol).

2. Similarly, maybe getaddrinfo() could return AF_INET first even though the user made a subclass with address_family = AF_INET6.

3. It seems a waste to do a DNS lookup just to choose the address_family and throw away the resolved addresses, only to then call bind() which will do the lookup all over again. If DNS times out, the delay until error reporting will be twice as long.

4. getaddrinfo(("", None)) has a short delay (DNS timeout?) for me. The Python documentation says the empty string "" is special-cased to mean INADDR_ANY (IPv4). It also says there is no special case for the IPv6 equivalent (in6addr_any), although it does seem to work in some cases, including bind(). But getaddrinfo() would parse the string in the underlying platform, not in Python.

Some ideas:

1. Don’t look up hostnames, and only determine IPv6 if a numerical IP address is specified. I think this closer to the original proposal. Maybe use AI_NUMERICHOST? Or the simplistic test proposed in Issue 24209?

2. For the empty string address "", if the platform supports dual stack, use AF_INET6, bind to “::” and clear the IPV6_V6ONLY option. See Issue 3213.

3. Bind sockets to all addresses returned by getaddrinfo(), not just the first. But this would be a much bigger and more radical change. Maybe just something to dream about. :)

4. Add an address_family parameter to the constructor, so the user can change to AF_INET6 without subclassing.
msg271871 - (view) Author: Paul Marks (Paul Marks) Date: 2016-08-03 02:42
First off, the server_address=('localhost', port) case: this feature is fundamentally broken without support for multiple sockets, because the server can listen on at most one address, and any single choice will often be inconsistent with clients' expectations.

For the (ip, port) case, the only question is whether an IPv4 address should create an AF_INET or dualstack (IPV6_V6ONLY=0) AF_INET6 socket; the latter needs a.b.c.d -> ::ffff:a.b.c.d translation.

Then there's the typical ('', port) case.  This should ideally create a server that accepts a connection on any supported family:
- If the system supports dualstack AF_INET6 sockets, use one.
- If the system only supports AF_INET sockets, use AF_INET.
- If the system only supports AF_INET6 sockets, use AF_INET6.
- If the system supports AF_INET and AF_INET6, but not dualstack, then you need multiple sockets.  Or go with AF_INET and hope nobody needs IPv6 on such a system.

(The legacy-free simple approach is to hard-code AF_INET6 and let the OS decide whether the socket should be dualstack or IPv6-only, but that only supports 2/4 of the above cases.)

Users of "conn, addr = self.get_request()" often expect addr to be an IPv4 2-tuple, especially when handling IPv4 requests.  So you have to normalize ('::ffff:192.0.2.1', 1234, 0, 0) to ('192.0.2.1', 1234).  For real IPv6 requests, it's often more convenient to keep returning 2-tuples ('2001:db8::1', 1234) instead of 4-tuples.

Another thing to watch out for is this HTTPServer code:
    host, port = self.socket.getsockname()[:2]
    self.server_name = socket.getfqdn(host)

getfqdn() has a special case for '0.0.0.0', which doesn't recognize the IPv6 equivalents:
    >>> import socket
    >>> socket.getfqdn('0.0.0.0')
   'redacted.corp.google.com'
    >>> socket.getfqdn('::')
   '::'
   >>> socket.getfqdn('::ffff:0.0.0.0')
   '::ffff:0.0.0.0'
msg271872 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-08-03 03:09
Paul, if the user specifically wants to bind to a numeric IPv4 address, is there any advantage of choosing the dual-stack IPV6_V6ONLY=0 mode with a mapped ::ffff:a.b.c.d address?
msg271873 - (view) Author: Paul Marks (Paul Marks) Date: 2016-08-03 03:54
> if the user specifically wants to bind to a numeric IPv4 address, is there any advantage of choosing the dual-stack [...]?

If you're in a position to write AF_INET6-only code, then dualstack sockets can make things a bit cleaner (one family for all IP communication).  But given that Python couldn't reasonably drop support for AF_INET-only systems, there's not a compelling reason to prefer dualstack sockets for IPv4 stuff.

They're just two windows into the same kernel code, so the decision is mostly arbitrary.

However, Python likes to expose IP addresses as plain strings without transparent ::ffff:0.0.0.0/96 handling, which tends to make dualstack sockets a leaky abstraction.  Ideally, you'd be able to talk to the kernel using AF_INET or AF_INET6 without normal users knowing the difference.
msg325416 - (view) Author: Nir Soffer (nirs) * Date: 2018-09-14 23:58
Doesn't it affect also 2.7, 3.6, 3.7, and 3.8?
History
Date User Action Args
2019-02-18 10:50:35giampaolo.rodolasetnosy: + giampaolo.rodola
2018-09-14 23:58:44nirssetmessages: + msg325416
2018-09-14 23:56:55nirssetnosy: + nirs
2016-11-29 23:54:50jpokornysetnosy: + jpokorny
2016-10-10 18:38:26jaracosetnosy: + jaraco
2016-08-03 03:54:38Paul Markssetmessages: + msg271873
2016-08-03 03:09:56martin.pantersetmessages: + msg271872
2016-08-03 02:42:07Paul Markssetmessages: + msg271871
2016-08-03 02:07:21martin.pantersetmessages: + msg271869
2016-08-03 00:57:12Paul Markssetnosy: + Paul Marks
2016-08-02 23:23:57gregory.p.smithsetfiles: + issue20215-gps01.diff

messages: + msg271865
title: socketserver can not listen IPv6 address -> socketserver.TCPServer can not listen IPv6 address
2016-08-02 22:44:01gregory.p.smithsetnosy: + gregory.p.smith
2016-03-05 16:07:24jleedevsetnosy: + jleedev
2015-05-23 04:01:34martin.pantersetnosy: + martin.panter

messages: + msg243884
title: Python2.7 socketserver can not listen IPv6 address -> socketserver can not listen IPv6 address
2014-11-19 14:29:19r.david.murraysetmessages: + msg231379
2014-11-19 12:45:38Carlos.Rallisetmessages: + msg231377
2014-11-19 10:08:10berker.peksagsetnosy: + berker.peksag

stage: patch review
2014-11-19 09:47:54Carlos.Rallisetnosy: + Carlos.Ralli
messages: + msg231363
2014-09-01 12:04:35pitrousetnosy: + neologix
2014-09-01 08:48:49andreasrsetmessages: + msg226219
2014-05-03 22:02:09andreasrsetfiles: + issue20215_socketserver.patch

nosy: + andreasr
messages: + msg217833

keywords: + patch
2014-01-10 02:22:38r.david.murraysetversions: + Python 3.5, - Python 2.7
nosy: + r.david.murray

messages: + msg207824

type: behavior -> enhancement
2014-01-10 02:03:02dazhaoyucreate