Commit caa053bd authored by Denis Bilenko's avatar Denis Bilenko

socket: improve gethostbyname and getaddrinfo; drop getnameinfo

- use evdns module
- gethostbyname()
  * check if hostname has no dots; if so, call the standard gethostbyname()
  * same goes if hostname is not of str type
- getaddrinfo():
  * check if host has no dots, not a string or is an IP address; if so, call getaddrinfo()
  * filter out the results based on passed socktype and proto arguments
  * add evdns_flags keyword argument that is passed unchanged to evdns functions
- getnameinfo() is gone; it did not match the original getnameinfo() API at all but
  was a proxy to dns_resolve_reverse(). Since the latter is now available
  in evdns module, this getnameinfo() is not needed
- rename internal variable _ip_re to _ip4_re
- remove unused variable BUFFER_SIZE
parent 1363c786
...@@ -38,7 +38,6 @@ __all__ = ['create_connection', ...@@ -38,7 +38,6 @@ __all__ = ['create_connection',
'gaierror', 'gaierror',
'getaddrinfo', 'getaddrinfo',
'gethostbyname', 'gethostbyname',
'getnameinfo',
'socket', 'socket',
'socketpair', 'socketpair',
'timeout', 'timeout',
...@@ -78,11 +77,10 @@ import time ...@@ -78,11 +77,10 @@ import time
import random import random
import re import re
from gevent.hub import getcurrent, get_hub, spawn_raw, Waiter from gevent.hub import getcurrent, get_hub, spawn_raw
from gevent import core from gevent import core
BUFFER_SIZE = 4096 _ip4_re = re.compile('^[\d\.]+$')
_ip_re = re.compile('^[\d\.]+$')
def _wait_helper(ev, evtype): def _wait_helper(ev, evtype):
...@@ -615,7 +613,7 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT): ...@@ -615,7 +613,7 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
msg = "getaddrinfo returns an empty list" msg = "getaddrinfo returns an empty list"
host, port = address host, port = address
for res in getaddrinfo_approx(host, port, 0, SOCK_STREAM): for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, _canonname, sa = res af, socktype, proto, _canonname, sa = res
sock = None sock = None
try: try:
...@@ -643,7 +641,7 @@ def create_connection_ssl(address, timeout=_GLOBAL_DEFAULT_TIMEOUT): ...@@ -643,7 +641,7 @@ def create_connection_ssl(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
msg = "getaddrinfo returns an empty list" msg = "getaddrinfo returns an empty list"
host, port = address host, port = address
for res in getaddrinfo_approx(host, port, 0, SOCK_STREAM): for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, _canonname, sa = res af, socktype, proto, _canonname, sa = res
sock = None sock = None
try: try:
...@@ -683,111 +681,88 @@ def wrap_ssl000(sock, keyfile=None, certfile=None): ...@@ -683,111 +681,88 @@ def wrap_ssl000(sock, keyfile=None, certfile=None):
return ssl_sock return ssl_sock
try: try:
core.dns_init() from gevent.evdns import dns_resolve_ipv4, dns_resolve_ipv6
except: except:
# fallback to blocking versions import traceback
gethostbyname = __socket__.gethostbyname traceback.print_exc()
getaddrinfo = __socket__.getaddrinfo __all__.remove('gethostbyname')
getnameinfo = __socket__.getnameinfo __all__.remove('getaddrinfo')
else: else:
# NOTE:
# use flags=core.DNS_QUERY_NO_SEARCH to avoid search, see comments in evdns.h
# TODO:
# might need to map evdns errors to socket errors
# for example, DNS_ERR_NOTEXIST(3) is:
# socket.gaierror: [Errno -2] Name or service not known
def _dns_helper(result, type, ttl, addrs, args):
(waiter,) = args
waiter.switch((result, type, ttl, addrs))
def gethostbyname(hostname): def gethostbyname(hostname):
"""gethostbyname implemented using EvDNS. """:func:`socket.gethostbyname` implemented using :mod:`evdns`.
Differs in the following ways: Differs in the following ways:
* raises gaierror with EvDNS error codes instead of standard socket error codes * raises :class:`DNSError` (a subclass of :class:`socket.gaierror`) with evdns error
* does not support /etc/hosts (see code for hacks to make localhost work) codes instead of standard socket error codes
* does not support ``/etc/hosts`` but calls the original :func:`socket.gethostbyname`
if *hostname* has no dots
* does not iterate through all addresses, instead picks a random one each time * does not iterate through all addresses, instead picks a random one each time
""" """
# TODO: this is supposed to iterate through all the addresses # TODO: this is supposed to iterate through all the addresses
# could use a global dict(hostname, iter) # could use a global dict(hostname, iter)
# - fix these nasty hacks for localhost, ips, etc. # - fix these nasty hacks for localhost, ips, etc.
if hostname == 'localhost': # QQQ should use /etc/hosts if not isinstance(hostname, str) or '.' not in hostname:
return '127.0.0.1' return _socket.gethostbyname(hostname)
if _ip_re.match(hostname): if _ip4_re.match(hostname):
return hostname return hostname
if hostname == _socket.gethostname(): if hostname == _socket.gethostname():
return _socket.gethostbyname(hostname) return _socket.gethostbyname(hostname)
waiter = Waiter() addrs = dns_resolve_ipv4(hostname)
core.dns_resolve_ipv4(hostname, 0, _dns_helper, waiter)
result, type, ttl, addrs = waiter.wait()
if result != core.DNS_ERR_NONE:
raise gaierror('dns_resolve_ipv4 returned %s' % result)
return random.choice(addrs) return random.choice(addrs)
def getaddrinfo(host, port, family=__socket__.AF_UNSPEC, socktype=__socket__.SOCK_STREAM, proto=0, flags=0):
"""getaddrinfo implemented using EvDNS. def getaddrinfo(host, port, *args, **kwargs):
"""*Some* approximation of :func:`socket.getaddrinfo` implemented using :mod:`evdns`.
If *host* is not a string, does not has any dots or is a numeric IP address, then
the standard :func:`socket.getaddrinfo` is called.
Otherwise, calls either :func:`dns_resolve_ipv4` or :func:`dns_resolve_ipv6` and
formats the result the way :func:`socket.getaddrinfo` does it.
Differs in the following ways: Differs in the following ways:
* raises gaierror with EvDNS error codes instead of standard socket error codes * raises :class:`DNSError` (a subclass of :class:`gaierror`) with evdns error
* does not support /etc/hosts codes instead of standard socket error codes
* IPv6 support is untested. * IPv6 support is untested.
* AF_UNSPEC only tries IPv4 * AF_UNSPEC only tries IPv4
* only supports TCP, UDP, IP protocols * only supports TCP, UDP, IP protocols
* port must be numeric, does not support string service names. see socket.getservbyname * port must be numeric, does not support string service names. see socket.getservbyname
* only supported value for flags is core.DNS_QUERY_NO_SEARCH, see evdns.h * *flags* argument is ignored
Additionally, supports *evdns_flags* keyword arguments (default ``0``) that is passed
to :mod:`evdns` functions.
""" """
if _ip_re.match(host): family, socktype, proto, _flags = args + (None, ) * (4 - len(args))
return [(__socket__.AF_INET, socktype, p, '', (host, port)) for p in (6, 17, 0)] if not isinstance(host, str) or '.' not in host or _ip4_re.match(host):
waiter = Waiter() return _socket.getaddrinfo(host, port, *args)
if family == __socket__.AF_INET:
core.dns_resolve_ipv4(host, flags, _dns_helper, waiter) evdns_flags = kwargs.pop('evdns_flags', 0)
elif family == __socket__.AF_INET6: if kwargs:
core.dns_resolve_ipv6(host, flags, _dns_helper, waiter) raise TypeError('Unsupported keyword arguments: %s' % (kwargs.keys(), ))
elif family == __socket__.AF_UNSPEC:
if family in (None, AF_INET, AF_UNSPEC):
family = AF_INET
# TODO: AF_UNSPEC means try both AF_INET and AF_INET6 # TODO: AF_UNSPEC means try both AF_INET and AF_INET6
family = __socket__.AF_INET addrs = dns_resolve_ipv4(host, evdns_flags)
core.dns_resolve_ipv4(host, flags, _dns_helper, waiter) elif family == AF_INET6:
addrs = dns_resolve_ipv6(host, evdns_flags)
else: else:
raise NotImplementedError raise NotImplementedError('family is not among AF_UNSPEC/AF_INET/AF_INET6: %r' % (family, ))
result, type, ttl, addrs = waiter.wait()
if result != core.DNS_ERR_NONE:
raise gaierror(result)
r = [] r = []
for addr in addrs:
for p in (6, 17, 0): # tcp, udp, ip protocols
r.append((family, socktype, p, '', (addr, port)))
return r
def getnameinfo(sockaddr, flags): socktype_proto = [(SOCK_STREAM, 6), (SOCK_DGRAM, 17), (SOCK_RAW, 0)]
"""getnameinfo implemented using EvDNS. if socktype is not None:
socktype_proto = [(x, y) for (x, y) in socktype_proto if socktype == x]
if proto is not None:
socktype_proto = [(x, y) for (x, y) in socktype_proto if proto == y]
Differs in the following ways: for addr in addrs:
for socktype, proto in socktype_proto:
* raises gaierror with EvDNS error codes instead of standard socket error codes r.append((family, socktype, proto, '', (addr, port)))
* does not support /etc/hosts return r
* IPv6 support is untested.
* port must be numeric, does not support string service names. see socket.getservbyname
* only supported value for flags is core.DNS_QUERY_NO_SEARCH, see evdns.h
"""
# http://svn.python.org/view/python/trunk/Modules/socketmodule.c?view=markup
# see socket_getnameinfo
try:
host, port = sockaddr[:2]
port = int(port)
except ValueError:
# make testRefCountGetNameInfo pass
del sockaddr
raise SystemError
waiter = Waiter()
core.dns_resolve_reverse(host, flags, _dns_helper, waiter)
result, type, ttl, addrs = waiter.wait()
if result != core.DNS_ERR_NONE:
raise gaierror(result)
return (addrs, port)
def ssl(sock, keyfile=None, certfile=None): def ssl(sock, keyfile=None, certfile=None):
......
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