|
16 | 16 |
|
17 | 17 | import collections
|
18 | 18 | import concurrent.futures
|
| 19 | +import functools |
19 | 20 | import heapq
|
20 | 21 | import inspect
|
| 22 | +import ipaddress |
21 | 23 | import itertools
|
22 | 24 | import logging
|
23 | 25 | import os
|
@@ -70,49 +72,83 @@ def _format_pipe(fd):
|
70 | 72 | return repr(fd)
|
71 | 73 |
|
72 | 74 |
|
| 75 | +# Linux's sock.type is a bitmask that can include extra info about socket. |
| 76 | +_SOCKET_TYPE_MASK = 0 |
| 77 | +if hasattr(socket, 'SOCK_NONBLOCK'): |
| 78 | + _SOCKET_TYPE_MASK |= socket.SOCK_NONBLOCK |
| 79 | +if hasattr(socket, 'SOCK_CLOEXEC'): |
| 80 | + _SOCKET_TYPE_MASK |= socket.SOCK_CLOEXEC |
| 81 | + |
| 82 | + |
| 83 | +@functools.lru_cache(maxsize=1024) |
| 84 | +def _ipaddr_info(host, port, family, type, proto): |
| 85 | + # Try to skip getaddrinfo if "host" is already an IP. Since getaddrinfo |
| 86 | + # blocks on an exclusive lock on some platforms, users might handle name |
| 87 | + # resolution in their own code and pass in resolved IPs. |
| 88 | + if proto not in {0, socket.IPPROTO_TCP, socket.IPPROTO_UDP} or host is None: |
| 89 | + return None |
| 90 | + |
| 91 | + type &= ~_SOCKET_TYPE_MASK |
| 92 | + if type == socket.SOCK_STREAM: |
| 93 | + proto = socket.IPPROTO_TCP |
| 94 | + elif type == socket.SOCK_DGRAM: |
| 95 | + proto = socket.IPPROTO_UDP |
| 96 | + else: |
| 97 | + return None |
| 98 | + |
| 99 | + if hasattr(socket, 'inet_pton'): |
| 100 | + if family == socket.AF_UNSPEC: |
| 101 | + afs = [socket.AF_INET, socket.AF_INET6] |
| 102 | + else: |
| 103 | + afs = [family] |
| 104 | + |
| 105 | + for af in afs: |
| 106 | + # Linux's inet_pton doesn't accept an IPv6 zone index after host, |
| 107 | + # like '::1%lo0', so strip it. If we happen to make an invalid |
| 108 | + # address look valid, we fail later in sock.connect or sock.bind. |
| 109 | + try: |
| 110 | + if af == socket.AF_INET6: |
| 111 | + socket.inet_pton(af, host.partition('%')[0]) |
| 112 | + else: |
| 113 | + socket.inet_pton(af, host) |
| 114 | + return af, type, proto, '', (host, port) |
| 115 | + except OSError: |
| 116 | + pass |
| 117 | + |
| 118 | + # "host" is not an IP address. |
| 119 | + return None |
| 120 | + |
| 121 | + # No inet_pton. (On Windows it's only available since Python 3.4.) |
| 122 | + # Even though getaddrinfo with AI_NUMERICHOST would be non-blocking, it |
| 123 | + # still requires a lock on some platforms, and waiting for that lock could |
| 124 | + # block the event loop. Use ipaddress instead, it's just text parsing. |
| 125 | + try: |
| 126 | + addr = ipaddress.IPv4Address(host) |
| 127 | + except ValueError: |
| 128 | + try: |
| 129 | + addr = ipaddress.IPv6Address(host.partition('%')[0]) |
| 130 | + except ValueError: |
| 131 | + return None |
| 132 | + |
| 133 | + af = socket.AF_INET if addr.version == 4 else socket.AF_INET6 |
| 134 | + if family not in (socket.AF_UNSPEC, af): |
| 135 | + # "host" is wrong IP version for "family". |
| 136 | + return None |
| 137 | + |
| 138 | + return af, type, proto, '', (host, port) |
| 139 | + |
| 140 | + |
73 | 141 | def _check_resolved_address(sock, address):
|
74 | 142 | # Ensure that the address is already resolved to avoid the trap of hanging
|
75 | 143 | # the entire event loop when the address requires doing a DNS lookup.
|
76 |
| - # |
77 |
| - # getaddrinfo() is slow (around 10 us per call): this function should only |
78 |
| - # be called in debug mode |
79 |
| - family = sock.family |
80 |
| - |
81 |
| - if family == socket.AF_INET: |
82 |
| - host, port = address |
83 |
| - elif family == socket.AF_INET6: |
84 |
| - host, port = address[:2] |
85 |
| - else: |
| 144 | + |
| 145 | + if hasattr(socket, 'AF_UNIX') and sock.family == socket.AF_UNIX: |
86 | 146 | return
|
87 | 147 |
|
88 |
| - # On Windows, socket.inet_pton() is only available since Python 3.4 |
89 |
| - if hasattr(socket, 'inet_pton'): |
90 |
| - # getaddrinfo() is slow and has known issue: prefer inet_pton() |
91 |
| - # if available |
92 |
| - try: |
93 |
| - socket.inet_pton(family, host) |
94 |
| - except OSError as exc: |
95 |
| - raise ValueError("address must be resolved (IP address), " |
96 |
| - "got host %r: %s" |
97 |
| - % (host, exc)) |
98 |
| - else: |
99 |
| - # Use getaddrinfo(flags=AI_NUMERICHOST) to ensure that the address is |
100 |
| - # already resolved. |
101 |
| - type_mask = 0 |
102 |
| - if hasattr(socket, 'SOCK_NONBLOCK'): |
103 |
| - type_mask |= socket.SOCK_NONBLOCK |
104 |
| - if hasattr(socket, 'SOCK_CLOEXEC'): |
105 |
| - type_mask |= socket.SOCK_CLOEXEC |
106 |
| - try: |
107 |
| - socket.getaddrinfo(host, port, |
108 |
| - family=family, |
109 |
| - type=(sock.type & ~type_mask), |
110 |
| - proto=sock.proto, |
111 |
| - flags=socket.AI_NUMERICHOST) |
112 |
| - except socket.gaierror as err: |
113 |
| - raise ValueError("address must be resolved (IP address), " |
114 |
| - "got host %r: %s" |
115 |
| - % (host, err)) |
| 148 | + host, port = address[:2] |
| 149 | + if _ipaddr_info(host, port, sock.family, sock.type, sock.proto) is None: |
| 150 | + raise ValueError("address must be resolved (IP address)," |
| 151 | + " got host %r" % host) |
116 | 152 |
|
117 | 153 |
|
118 | 154 | def _run_until_complete_cb(fut):
|
@@ -535,7 +571,12 @@ def _getaddrinfo_debug(self, host, port, family, type, proto, flags):
|
535 | 571 |
|
536 | 572 | def getaddrinfo(self, host, port, *,
|
537 | 573 | family=0, type=0, proto=0, flags=0):
|
538 |
| - if self._debug: |
| 574 | + info = _ipaddr_info(host, port, family, type, proto) |
| 575 | + if info is not None: |
| 576 | + fut = futures.Future(loop=self) |
| 577 | + fut.set_result([info]) |
| 578 | + return fut |
| 579 | + elif self._debug: |
539 | 580 | return self.run_in_executor(None, self._getaddrinfo_debug,
|
540 | 581 | host, port, family, type, proto, flags)
|
541 | 582 | else:
|
|
0 commit comments