-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
socketserver.TCPServer can not listen IPv6 address #64414
Comments
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 |
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. |
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. |
Is there any interest in this patch? it would be nice with a review of the patch. :) |
Is it possible to use this patch for python2.7 ? |
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>:
|
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. |
I’m no IPv6 expert, but see Lib/smtpd.py:657 and bpo-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. |
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. |
I also have reservations about using getaddrinfo() like this. Some potential problems:
Some ideas:
|
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:
(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: 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' |
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? |
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. |
Doesn't it affect also 2.7, 3.6, 3.7, and 3.8? |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: