Commit 0f20b883 authored by Jason Madden's avatar Jason Madden

Pass addresses through inet_pton in connect() before trying to pass them to getaddrinfo().

The latter can lose information, like IPv6 scope IDs given in the string form.
parent f19927e8
``gevent.socket.create_connection`` and
``gevent.socket.socket.connect`` no longer ignore IPv6 scope IDs.
......@@ -251,6 +251,14 @@ class socket(_socketcommon.SocketMixin):
return isinstance(self._sock, _closedsocket)
def connect(self, address):
"""
Connect to *address*.
.. versionchanged:: NEXT
If the host part of the address includes an IPv6 scope ID,
it will be used instead of ignored, if the platform supplies
:func:`socket.inet_pton`.
"""
if self.timeout == 0.0:
return self._sock.connect(address)
......
......@@ -389,10 +389,17 @@ class socket(_socketcommon.SocketMixin):
return sock.detach()
def connect(self, address):
"""
Connect to *address*.
.. versionchanged:: NEXT
If the host part of the address includes an IPv6 scope ID,
it will be used instead of ignored, if the platform supplies
:func:`socket.inet_pton`.
"""
if self.timeout == 0.0:
return _socket.socket.connect(self._sock, address)
address = _socketcommon._resolve_addr(self._sock, address)
with Timeout._start_new_or_dummy(self.timeout, timeout("timed out")):
while True:
err = self.getsockopt(SOL_SOCKET, SO_ERROR)
......
......@@ -398,6 +398,19 @@ def _resolve_addr(sock, address):
if sock.family not in _RESOLVABLE_FAMILIES or not isinstance(address, tuple):
return address
# address is (host, port) (ipv4) or (host, port, flowinfo, scopeid) (ipv6).
# If it's already resolved, no need to go through getaddrinfo() again.
# That can lose precision (e.g., on IPv6, it can lose scopeid). The standard library
# does this in socketmodule.c:setipaddr.
try:
if __socket__.inet_pton(sock.family, address[0]):
return address
except AttributeError: # pragma: no cover
# inet_pton might not be available.
pass
except __socket__.error:
# Not parseable, needs resolved.
pass
# We don't pass the port to getaddrinfo because the C
# socket module doesn't either (on some systems its
......
......@@ -75,6 +75,11 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=N
must be a tuple of (host, port) for the socket to bind as a source
address before making the connection. A host of '' or port 0 tells
the OS to use the default.
.. versionchanged:: NEXT
If the host part of the address includes an IPv6 scope ID,
it will be used instead of ignored, if the platform supplies
:func:`socket.inet_pton`.
"""
host, port = address
......@@ -85,7 +90,7 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=N
raise error("getaddrinfo returns an empty list")
for res in addrs:
af, socktype, proto, _, sa = res
af, socktype, proto, _canonname, sa = res
sock = None
try:
sock = socket(af, socktype, proto)
......
......@@ -562,6 +562,19 @@ class TestFunctions(greentest.TestCase):
exclude.append('gethostbyaddr')
self.assertMonkeyPatchedFuncSignatures('socket', exclude=exclude)
def test_resolve_ipv6_scope_id(self):
from gevent import _socketcommon as SC
if not SC.__socket__.has_ipv6:
self.skipTest("Needs IPv6") # pragma: no cover
if not hasattr(SC.__socket__, 'inet_pton'):
self.skipTest("Needs inet_pton") # pragma: no cover
# A valid IPv6 address, with a scope.
addr = ('2607:f8b0:4000:80e::200e', 80, 0, 9)
# Mock socket
class sock(object):
family = SC.AF_INET6 # pylint:disable=no-member
self.assertIs(addr, SC._resolve_addr(sock, addr))
class TestSocket(greentest.TestCase):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment