Upgrade bundled dnspython to fix DNS resolution

The version introduced in commit [1] ([2]) has a major bug - "The DNS
resolver doesn't return any records and under some circumstances throws
KeyError exceptions from within dnspython" [3]. dnspython commit [4]
fixes it so let's update to the latest development version.

Simple script to reproduce:

    import eventlet
    eventlet.monkey_patch(all=True)

    import socket
    print(socket.gethostbyname('google.co.uk'))

Before this change it'd raise an exception, after - it produces
a result.

[1] 52b09becac
[2] 188aa701a6
[3] https://github.com/rthalley/dnspython/issues/206
[4] 292995db18
This commit is contained in:
Jakub Stasiak
2016-09-27 11:18:07 +01:00
parent 1d4ce40c1b
commit 9a13405598
36 changed files with 291 additions and 118 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/bash -eux #!/bin/bash -eux
cd "$( dirname "${BASH_SOURCE[0]}" )/.." cd "$( dirname "${BASH_SOURCE[0]}" )/.."
version=${1-188aa701a6826c607da0624e31a8c4618d0a8017} version=${1-bb0c9f21f4a6f56f2fe8d7c1fc991080ef89d223}
upstream_path=./dnspython-${version} upstream_path=./dnspython-${version}
if [[ ! -d "${upstream_path}" ]]; then if [[ ! -d "${upstream_path}" ]]; then
curl -L -odnspython.zip "https://github.com/rthalley/dnspython/archive/${version}.zip" curl -L -odnspython.zip "https://github.com/rthalley/dnspython/archive/${version}.zip"

View File

@@ -70,14 +70,14 @@ class EntropyPool(object):
if not self.seeded or self.seed_pid != os.getpid(): if not self.seeded or self.seed_pid != os.getpid():
try: try:
seed = os.urandom(16) seed = os.urandom(16)
except: except Exception:
try: try:
r = open('/dev/urandom', 'rb', 0) r = open('/dev/urandom', 'rb', 0)
try: try:
seed = r.read(16) seed = r.read(16)
finally: finally:
r.close() r.close()
except: except Exception:
seed = str(time.time()) seed = str(time.time())
self.seeded = True self.seeded = True
self.seed_pid = os.getpid() self.seed_pid = os.getpid()
@@ -125,7 +125,7 @@ pool = EntropyPool()
try: try:
system_random = random.SystemRandom() system_random = random.SystemRandom()
except: except Exception:
system_random = None system_random = None
def random_16(): def random_16():

View File

@@ -23,14 +23,14 @@ class DNSException(Exception):
It supports two basic modes of operation: It supports two basic modes of operation:
a) Old/compatible mode is used if __init__ was called with a) Old/compatible mode is used if __init__ was called with
empty **kwargs. empty **kwargs.
In compatible mode all *args are passed to standard Python Exception class In compatible mode all *args are passed to standard Python Exception class
as before and all *args are printed by standard __str__ implementation. as before and all *args are printed by standard __str__ implementation.
Class variable msg (or doc string if msg is None) is returned from str() Class variable msg (or doc string if msg is None) is returned from str()
if *args is empty. if *args is empty.
b) New/parametrized mode is used if __init__ was called with b) New/parametrized mode is used if __init__ was called with
non-empty **kwargs. non-empty **kwargs.
In the new mode *args has to be empty and all kwargs has to exactly match In the new mode *args has to be empty and all kwargs has to exactly match
set in class variable self.supp_kwargs. All kwargs are stored inside set in class variable self.supp_kwargs. All kwargs are stored inside
self.kwargs and used in new __str__ implementation to construct self.kwargs and used in new __str__ implementation to construct

View File

@@ -85,7 +85,7 @@ def af_for_address(text):
try: try:
dns.ipv4.inet_aton(text) dns.ipv4.inet_aton(text)
return AF_INET return AF_INET
except: except Exception:
try: try:
dns.ipv6.inet_aton(text) dns.ipv6.inet_aton(text)
return AF_INET6 return AF_INET6
@@ -103,9 +103,9 @@ def is_multicast(text):
try: try:
first = ord(dns.ipv4.inet_aton(text)[0]) first = ord(dns.ipv4.inet_aton(text)[0])
return first >= 224 and first <= 239 return first >= 224 and first <= 239
except: except Exception:
try: try:
first = ord(dns.ipv6.inet_aton(text)[0]) first = ord(dns.ipv6.inet_aton(text)[0])
return first == 255 return first == 255
except: except Exception:
raise ValueError raise ValueError

View File

@@ -898,7 +898,7 @@ class _TextReader(object):
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except dns.exception.SyntaxError: except dns.exception.SyntaxError:
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except: except Exception:
rdclass = dns.rdataclass.IN rdclass = dns.rdataclass.IN
# Type # Type
rdtype = dns.rdatatype.from_text(token.value) rdtype = dns.rdatatype.from_text(token.value)
@@ -931,7 +931,7 @@ class _TextReader(object):
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except dns.exception.SyntaxError: except dns.exception.SyntaxError:
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except: except Exception:
ttl = 0 ttl = 0
# Class # Class
try: try:
@@ -944,7 +944,7 @@ class _TextReader(object):
rdclass = self.zone_rdclass rdclass = self.zone_rdclass
except dns.exception.SyntaxError: except dns.exception.SyntaxError:
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except: except Exception:
rdclass = dns.rdataclass.IN rdclass = dns.rdataclass.IN
# Type # Type
rdtype = dns.rdatatype.from_text(token.value) rdtype = dns.rdatatype.from_text(token.value)

View File

@@ -26,6 +26,11 @@ import struct
import sys import sys
import copy import copy
import encodings.idna import encodings.idna
try:
import idna
have_idna_2008 = True
except ImportError:
have_idna_2008 = False
import dns.exception import dns.exception
import dns.wiredata import dns.wiredata
@@ -34,7 +39,7 @@ from ._compat import long, binary_type, text_type, unichr
try: try:
maxint = sys.maxint maxint = sys.maxint
except: except AttributeError:
maxint = (1 << (8 * struct.calcsize("P"))) // 2 - 1 maxint = (1 << (8 * struct.calcsize("P"))) // 2 - 1
NAMERELN_NONE = 0 NAMERELN_NONE = 0
@@ -91,8 +96,115 @@ class NoParent(dns.exception.DNSException):
"""An attempt was made to get the parent of the root name """An attempt was made to get the parent of the root name
or the empty name.""" or the empty name."""
class NoIDNA2008(dns.exception.DNSException):
"""IDNA 2008 processing was requested but the idna module is not
available."""
class IDNAException(dns.exception.DNSException):
"""IDNA 2008 processing raised an exception."""
supp_kwargs = set(['idna_exception'])
fmt = "IDNA processing exception: {idna_exception}"
class IDNACodec(object):
"""Abstract base class for IDNA encoder/decoders."""
def encode(self, label):
raise NotImplementedError
def decode(self, label):
raise NotImplementedError
class IDNA2003Codec(IDNACodec):
"""IDNA 2003 encoder/decoder."""
def encode(self, label):
if label == '':
return b''
try:
return encodings.idna.ToASCII(label)
except UnicodeError:
raise LabelTooLong
def decode(self, label):
if label == b'':
return u''
return _escapify(encodings.idna.ToUnicode(label), True)
class IDNA2008Codec(IDNACodec):
"""IDNA 2008 encoder/decoder."""
def __init__(self, uts_46=False, transitional=False,
allow_pure_ascii=False):
"""Initialize the IDNA 2008 encoder/decoder.
@param uts_46: If True, apply Unicode IDNA compatibility processing
as described in Unicode Technical Standard #46
(U{http://unicode.org/reports/tr46/}). This parameter is only
meaningful if IDNA 2008 is in use. If False, do not apply
the mapping. The default is False
@type uts_46: bool
@param transitional: If True, use the "transitional" mode described
in Unicode Technical Standard #46. This parameter is only
meaningful if IDNA 2008 is in use. The default is False.
@type transitional: bool
@param allow_pure_ascii: If True, then a label which
consists of only ASCII characters is allowed. This is less strict
than regular IDNA 2008, but is also necessary for mixed names,
e.g. a name with starting with "_sip._tcp." and ending in an IDN
suffixm which would otherwise be disallowed. The default is False
@type allow_pure_ascii: bool
"""
self.uts_46 = uts_46
self.transitional = transitional
self.allow_pure_ascii = allow_pure_ascii
def is_all_ascii(self, label):
for c in label:
if ord(c) > 0x7f:
return False
return True
def encode(self, label):
if label == '':
return b''
if self.allow_pure_ascii and self.is_all_ascii(label):
return label.encode('ascii')
if not have_idna_2008:
raise NoIDNA2008
try:
if self.uts_46:
label = idna.uts46_remap(label, False, self.transitional)
return idna.alabel(label)
except idna.IDNAError as e:
raise IDNAException(idna_exception=e)
def decode(self, label):
if label == b'':
return u''
if not have_idna_2008:
raise NoIDNA2008
try:
if self.uts_46:
label = idna.uts46_remap(label, False, False)
return _escapify(idna.ulabel(label), True)
except idna.IDNAError as e:
raise IDNAException(idna_exception=e)
_escaped = bytearray(b'"().;\\@$') _escaped = bytearray(b'"().;\\@$')
IDNA_2003 = IDNA2003Codec()
IDNA_2008_Practical = IDNA2008Codec(True, False, True)
IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False)
IDNA_2008_Strict = IDNA2008Codec(False, False, False)
IDNA_2008_Transitional = IDNA2008Codec(True, True, False)
IDNA_2008 = IDNA_2008_Practical
def _escapify(label, unicode_mode=False): def _escapify(label, unicode_mode=False):
"""Escape the characters in label which need it. """Escape the characters in label which need it.
@@ -126,7 +238,6 @@ def _escapify(label, unicode_mode=False):
text += u'\\%03d' % ord(c) text += u'\\%03d' % ord(c)
return text return text
def _validate_labels(labels): def _validate_labels(labels):
"""Check for empty labels in the middle of a label sequence, """Check for empty labels in the middle of a label sequence,
labels that are too long, and for too many labels. labels that are too long, and for too many labels.
@@ -375,13 +486,18 @@ class Name(object):
s = b'.'.join(map(_escapify, l)) s = b'.'.join(map(_escapify, l))
return s return s
def to_unicode(self, omit_final_dot=False): def to_unicode(self, omit_final_dot=False, idna_codec=None):
"""Convert name to Unicode text format. """Convert name to Unicode text format.
IDN ACE labels are converted to Unicode. IDN ACE labels are converted to Unicode.
@param omit_final_dot: If True, don't emit the final dot (denoting the @param omit_final_dot: If True, don't emit the final dot (denoting the
root label) for absolute names. The default is False. root label) for absolute names. The default is False.
@type omit_final_dot: bool
@param: idna_codec: IDNA encoder/decoder. If None, the default IDNA
2003
encoder/decoder is used.
@type idna_codec: dns.name.IDNA
@rtype: string @rtype: string
""" """
@@ -393,9 +509,9 @@ class Name(object):
l = self.labels[:-1] l = self.labels[:-1]
else: else:
l = self.labels l = self.labels
s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True) if idna_codec is None:
for x in l]) idna_codec = IDNA_2003
return s return u'.'.join([idna_codec.decode(x) for x in l])
def to_digestable(self, origin=None): def to_digestable(self, origin=None):
"""Convert name to a format suitable for digesting in hashes. """Convert name to a format suitable for digesting in hashes.
@@ -488,9 +604,6 @@ class Name(object):
def __getitem__(self, index): def __getitem__(self, index):
return self.labels[index] return self.labels[index]
def __getslice__(self, start, stop):
return self.labels[start:stop]
def __add__(self, other): def __add__(self, other):
return self.concatenate(other) return self.concatenate(other)
@@ -583,11 +696,18 @@ root = Name([b''])
empty = Name([]) empty = Name([])
def from_unicode(text, origin=root): def from_unicode(text, origin=root, idna_codec=None):
"""Convert unicode text into a Name object. """Convert unicode text into a Name object.
Labels are encoded in IDN ACE form. Labels are encoded in IDN ACE form.
@param text: The text to convert into a name.
@type text: Unicode string
@param origin: The origin to append to non-absolute names.
@type origin: dns.name.Name
@param: idna_codec: IDNA encoder/decoder. If None, the default IDNA 2003
encoder/decoder is used.
@type idna_codec: dns.name.IDNA
@rtype: dns.name.Name object @rtype: dns.name.Name object
""" """
@@ -600,6 +720,8 @@ def from_unicode(text, origin=root):
escaping = False escaping = False
edigits = 0 edigits = 0
total = 0 total = 0
if idna_codec is None:
idna_codec = IDNA_2003
if text == u'@': if text == u'@':
text = u'' text = u''
if text: if text:
@@ -626,10 +748,7 @@ def from_unicode(text, origin=root):
elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']: elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']:
if len(label) == 0: if len(label) == 0:
raise EmptyLabel raise EmptyLabel
try: labels.append(idna_codec.encode(label))
labels.append(encodings.idna.ToASCII(label))
except UnicodeError:
raise LabelTooLong
label = u'' label = u''
elif c == u'\\': elif c == u'\\':
escaping = True escaping = True
@@ -640,10 +759,7 @@ def from_unicode(text, origin=root):
if escaping: if escaping:
raise BadEscape raise BadEscape
if len(label) > 0: if len(label) > 0:
try: labels.append(idna_codec.encode(label))
labels.append(encodings.idna.ToASCII(label))
except UnicodeError:
raise LabelTooLong
else: else:
labels.append(b'') labels.append(b'')
@@ -652,13 +768,21 @@ def from_unicode(text, origin=root):
return Name(labels) return Name(labels)
def from_text(text, origin=root): def from_text(text, origin=root, idna_codec=None):
"""Convert text into a Name object. """Convert text into a Name object.
@param text: The text to convert into a name.
@type text: string
@param origin: The origin to append to non-absolute names.
@type origin: dns.name.Name
@param: idna_codec: IDNA encoder/decoder. If None, the default IDNA 2003
encoder/decoder is used.
@type idna_codec: dns.name.IDNA
@rtype: dns.name.Name object @rtype: dns.name.Name object
""" """
if isinstance(text, text_type): if isinstance(text, text_type):
return from_unicode(text, origin) return from_unicode(text, origin, idna_codec)
if not isinstance(text, binary_type): if not isinstance(text, binary_type):
raise ValueError("input to from_text() must be a string") raise ValueError("input to from_text() must be a string")
if not (origin is None or isinstance(origin, Name)): if not (origin is None or isinstance(origin, Name)):

View File

@@ -176,7 +176,7 @@ def _destination_and_source(af, where, port, source, source_port):
if af is None: if af is None:
try: try:
af = dns.inet.af_for_address(where) af = dns.inet.af_for_address(where)
except: except Exception:
af = dns.inet.AF_INET af = dns.inet.AF_INET
if af == dns.inet.AF_INET: if af == dns.inet.AF_INET:
destination = (where, port) destination = (where, port)

View File

@@ -96,6 +96,7 @@ MAILA = 254
ANY = 255 ANY = 255
URI = 256 URI = 256
CAA = 257 CAA = 257
AVC = 258
TA = 32768 TA = 32768
DLV = 32769 DLV = 32769
@@ -166,6 +167,7 @@ _by_text = {
'ANY': ANY, 'ANY': ANY,
'URI': URI, 'URI': URI,
'CAA': CAA, 'CAA': CAA,
'AVC': AVC,
'TA': TA, 'TA': TA,
'DLV': DLV, 'DLV': DLV,
} }

View File

@@ -0,0 +1,23 @@
# Copyright (C) 2016 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import dns.rdtypes.txtbase
class AVC(dns.rdtypes.txtbase.TXTBase):
"""AVC record
@see: U{http://www.iana.org/assignments/dns-parameters/AVC/avc-completed-template}"""

View File

@@ -71,4 +71,3 @@ class CAA(dns.rdata.Rdata):
tag = wire[current: current + l] tag = wire[current: current + l]
value = wire[current + l:current + rdlen - 2] value = wire[current + l:current + rdlen - 2]
return cls(rdclass, rdtype, flags, tag, value) return cls(rdclass, rdtype, flags, tag, value)

View File

@@ -119,4 +119,3 @@ class CERT(dns.rdata.Rdata):
certificate = wire[current: current + rdlen].unwrap() certificate = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, return cls(rdclass, rdtype, certificate_type, key_tag, algorithm,
certificate) certificate)

View File

@@ -82,4 +82,3 @@ class HINFO(dns.rdata.Rdata):
raise dns.exception.FormError raise dns.exception.FormError
os = wire[current: current + l].unwrap() os = wire[current: current + l].unwrap()
return cls(rdclass, rdtype, cpu, os) return cls(rdclass, rdtype, cpu, os)

View File

@@ -95,4 +95,3 @@ class ISDN(dns.rdata.Rdata):
else: else:
subaddress = '' subaddress = ''
return cls(rdclass, rdtype, address, subaddress) return cls(rdclass, rdtype, address, subaddress)

View File

@@ -138,16 +138,12 @@ class LOC(dns.rdata.Rdata):
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
if self.latitude[4] > 0: if self.latitude[4] > 0:
lat_hemisphere = 'N' lat_hemisphere = 'N'
lat_degrees = self.latitude[0]
else: else:
lat_hemisphere = 'S' lat_hemisphere = 'S'
lat_degrees = -1 * self.latitude[0]
if self.longitude[4] > 0: if self.longitude[4] > 0:
long_hemisphere = 'E' long_hemisphere = 'E'
long_degrees = self.longitude[0]
else: else:
long_hemisphere = 'W' long_hemisphere = 'W'
long_degrees = -1 * self.longitude[0]
text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % ( text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % (
self.latitude[0], self.latitude[1], self.latitude[0], self.latitude[1],
self.latitude[2], self.latitude[3], lat_hemisphere, self.latitude[2], self.latitude[3], lat_hemisphere,

View File

@@ -189,4 +189,3 @@ class NSEC3(dns.rdata.Rdata):
windows.append((window, bitmap)) windows.append((window, bitmap))
return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next,
windows) windows)

View File

@@ -86,4 +86,3 @@ class NSEC3PARAM(dns.rdata.Rdata):
if rdlen != 0: if rdlen != 0:
raise dns.exception.FormError raise dns.exception.FormError
return cls(rdclass, rdtype, algorithm, flags, iterations, salt) return cls(rdclass, rdtype, algorithm, flags, iterations, salt)

View File

@@ -75,4 +75,3 @@ class SSHFP(dns.rdata.Rdata):
rdlen -= 2 rdlen -= 2
fingerprint = wire[current: current + rdlen].unwrap() fingerprint = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, header[0], header[1], fingerprint) return cls(rdclass, rdtype, header[0], header[1], fingerprint)

View File

@@ -80,4 +80,3 @@ class TLSA(dns.rdata.Rdata):
rdlen -= 3 rdlen -= 3
cert = wire[current: current + rdlen].unwrap() cert = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, header[0], header[1], header[2], cert) return cls(rdclass, rdtype, header[0], header[1], header[2], cert)

View File

@@ -78,4 +78,3 @@ class URI(dns.rdata.Rdata):
current += rdlen current += rdlen
return cls(rdclass, rdtype, priority, weight, target) return cls(rdclass, rdtype, priority, weight, target)

View File

@@ -62,4 +62,3 @@ class X25(dns.rdata.Rdata):
raise dns.exception.FormError raise dns.exception.FormError
address = wire[current: current + l].unwrap() address = wire[current: current + l].unwrap()
return cls(rdclass, rdtype, address) return cls(rdclass, rdtype, address)

View File

@@ -50,4 +50,3 @@ class A(dns.rdata.Rdata):
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
address = dns.ipv4.inet_ntoa(wire[current: current + rdlen]).decode() address = dns.ipv4.inet_ntoa(wire[current: current + rdlen]).decode()
return cls(rdclass, rdtype, address) return cls(rdclass, rdtype, address)

View File

@@ -51,4 +51,3 @@ class AAAA(dns.rdata.Rdata):
address = dns.inet.inet_ntop(dns.inet.AF_INET6, address = dns.inet.inet_ntop(dns.inet.AF_INET6,
wire[current: current + rdlen]) wire[current: current + rdlen])
return cls(rdclass, rdtype, address) return cls(rdclass, rdtype, address)

View File

@@ -159,4 +159,3 @@ class APL(dns.rdata.Rdata):
item = APLItem(header[0], negation, address, header[1]) item = APLItem(header[0], negation, address, header[1])
items.append(item) items.append(item)
return cls(rdclass, rdtype, items) return cls(rdclass, rdtype, items)

View File

@@ -57,4 +57,3 @@ class DHCID(dns.rdata.Rdata):
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
data = wire[current: current + rdlen].unwrap() data = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, data) return cls(rdclass, rdtype, data)

View File

@@ -146,4 +146,3 @@ class IPSECKEY(dns.rdata.Rdata):
key = wire[current: current + rdlen].unwrap() key = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, header[0], gateway_type, header[2], return cls(rdclass, rdtype, header[0], gateway_type, header[2],
gateway, key) gateway, key)

View File

@@ -56,4 +56,3 @@ class NSAP(dns.rdata.Rdata):
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
address = wire[current: current + rdlen].unwrap() address = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, address) return cls(rdclass, rdtype, address)

View File

@@ -103,4 +103,3 @@ class WKS(dns.rdata.Rdata):
rdlen -= 5 rdlen -= 5
bitmap = wire[current: current + rdlen].unwrap() bitmap = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, address, protocol, bitmap) return cls(rdclass, rdtype, address, protocol, bitmap)

View File

@@ -81,4 +81,3 @@ class DSBase(dns.rdata.Rdata):
rdlen -= 4 rdlen -= 4
digest = wire[current: current + rdlen].unwrap() digest = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, header[0], header[1], header[2], digest) return cls(rdclass, rdtype, header[0], header[1], header[2], digest)

View File

@@ -69,4 +69,3 @@ class EUIBase(dns.rdata.Rdata):
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
eui = wire[current:current + rdlen].unwrap() eui = wire[current:current + rdlen].unwrap()
return cls(rdclass, rdtype, eui) return cls(rdclass, rdtype, eui)

View File

@@ -88,4 +88,3 @@ class TXTBase(dns.rdata.Rdata):
rdlen -= l rdlen -= l
strings.append(s) strings.append(s)
return cls(rdclass, rdtype, strings) return cls(rdclass, rdtype, strings)

View File

@@ -135,8 +135,9 @@ class NoNameservers(dns.exception.DNSException):
"""All nameservers failed to answer the query. """All nameservers failed to answer the query.
@param errors: list of servers and respective errors errors: list of servers and respective errors
@type errors: [(server ip address, any object convertible to string)] The type of errors is
[(server ip address, any object convertible to string)].
Non-empty errors list will add explanatory message () Non-empty errors list will add explanatory message ()
""" """
@@ -271,10 +272,10 @@ class Answer(object):
raise AttributeError(attr) raise AttributeError(attr)
def __len__(self): def __len__(self):
return len(self.rrset) return self.rrset and len(self.rrset) or 0
def __iter__(self): def __iter__(self):
return iter(self.rrset) return self.rrset and iter(self.rrset) or iter(tuple())
def __getitem__(self, i): def __getitem__(self, i):
return self.rrset[i] return self.rrset[i]
@@ -282,9 +283,6 @@ class Answer(object):
def __delitem__(self, i): def __delitem__(self, i):
del self.rrset[i] del self.rrset[i]
def __getslice__(self, i, j):
return self.rrset[i:j]
class Cache(object): class Cache(object):
@@ -434,7 +432,7 @@ class LRUCache(object):
"""Initialize a DNS cache. """Initialize a DNS cache.
@param max_size: The maximum number of nodes to cache; the default is @param max_size: The maximum number of nodes to cache; the default is
100000. Must be > 1. 100,000. Must be greater than 1.
@type max_size: int @type max_size: int
""" """
self.data = {} self.data = {}
@@ -581,6 +579,24 @@ class Resolver(object):
POSIX systems and from the registry on Windows systems.) POSIX systems and from the registry on Windows systems.)
@type configure: bool""" @type configure: bool"""
self.domain = None
self.nameservers = None
self.nameserver_ports = None
self.port = None
self.search = None
self.timeout = None
self.lifetime = None
self.keyring = None
self.keyname = None
self.keyalgorithm = None
self.edns = None
self.ednsflags = None
self.payload = None
self.cache = None
self.flags = None
self.retry_servfail = False
self.rotate = False
self.reset() self.reset()
if configure: if configure:
if sys.platform == 'win32': if sys.platform == 'win32':
@@ -901,12 +917,13 @@ class Resolver(object):
all_nxdomain = True all_nxdomain = True
nxdomain_responses = {} nxdomain_responses = {}
start = time.time() start = time.time()
_qname = None # make pylint happy
for _qname in qnames_to_try: for _qname in qnames_to_try:
if self.cache: if self.cache:
answer = self.cache.get((_qname, rdtype, rdclass)) answer = self.cache.get((_qname, rdtype, rdclass))
if answer is not None: if answer is not None:
if answer.rrset is None and raise_on_no_answer: if answer.rrset is None and raise_on_no_answer:
raise NoAnswer raise NoAnswer(response=answer.response)
else: else:
return answer return answer
request = dns.message.make_query(_qname, rdtype, rdclass) request = dns.message.make_query(_qname, rdtype, rdclass)
@@ -1032,10 +1049,10 @@ class Resolver(object):
break break
if all_nxdomain: if all_nxdomain:
raise NXDOMAIN(qnames=qnames_to_try, responses=nxdomain_responses) raise NXDOMAIN(qnames=qnames_to_try, responses=nxdomain_responses)
answer = Answer(qname, rdtype, rdclass, response, answer = Answer(_qname, rdtype, rdclass, response,
raise_on_no_answer) raise_on_no_answer)
if self.cache: if self.cache:
self.cache.put((qname, rdtype, rdclass), answer) self.cache.put((_qname, rdtype, rdclass), answer)
return answer return answer
def use_tsig(self, keyring, keyname=None, def use_tsig(self, keyring, keyname=None,
@@ -1194,13 +1211,13 @@ def _getaddrinfo(host=None, service=None, family=socket.AF_UNSPEC, socktype=0,
addr = dns.ipv6.inet_aton(ahost) addr = dns.ipv6.inet_aton(ahost)
v6addrs.append(host) v6addrs.append(host)
canonical_name = host canonical_name = host
except: except Exception:
try: try:
# Is it a V4 address literal? # Is it a V4 address literal?
addr = dns.ipv4.inet_aton(host) addr = dns.ipv4.inet_aton(host)
v4addrs.append(host) v4addrs.append(host)
canonical_name = host canonical_name = host
except: except Exception:
if flags & socket.AI_NUMERICHOST == 0: if flags & socket.AI_NUMERICHOST == 0:
try: try:
if family == socket.AF_INET6 or family == socket.AF_UNSPEC: if family == socket.AF_INET6 or family == socket.AF_UNSPEC:
@@ -1232,11 +1249,11 @@ def _getaddrinfo(host=None, service=None, family=socket.AF_UNSPEC, socktype=0,
port = 0 port = 0
else: else:
port = int(service) port = int(service)
except: except Exception:
if flags & socket.AI_NUMERICSERV == 0: if flags & socket.AI_NUMERICSERV == 0:
try: try:
port = socket.getservbyname(service) port = socket.getservbyname(service)
except: except Exception:
pass pass
if port is None: if port is None:
raise socket.gaierror(socket.EAI_NONAME) raise socket.gaierror(socket.EAI_NONAME)
@@ -1311,7 +1328,7 @@ def _getfqdn(name=None):
name = socket.gethostname() name = socket.gethostname()
try: try:
return _getnameinfo(_getaddrinfo(name, 80)[0][4])[0] return _getnameinfo(_getaddrinfo(name, 80)[0][4])[0]
except: except Exception:
return name return name
@@ -1336,7 +1353,7 @@ def _gethostbyaddr(ip):
dns.ipv6.inet_aton(ip) dns.ipv6.inet_aton(ip)
sockaddr = (ip, 80, 0, 0) sockaddr = (ip, 80, 0, 0)
family = socket.AF_INET6 family = socket.AF_INET6
except: except Exception:
sockaddr = (ip, 80) sockaddr = (ip, 80)
family = socket.AF_INET family = socket.AF_INET
(name, port) = _getnameinfo(sockaddr, socket.NI_NAMEREQD) (name, port) = _getnameinfo(sockaddr, socket.NI_NAMEREQD)

View File

@@ -51,7 +51,7 @@ def from_address(text):
else: else:
parts = [x for x in str(binascii.hexlify(v6).decode())] parts = [x for x in str(binascii.hexlify(v6).decode())]
origin = ipv6_reverse_domain origin = ipv6_reverse_domain
except: except Exception:
parts = ['%d' % parts = ['%d' %
byte for byte in bytearray(dns.ipv4.inet_aton(text))] byte for byte in bytearray(dns.ipv4.inet_aton(text))]
origin = ipv4_reverse_domain origin = ipv4_reverse_domain

View File

@@ -119,7 +119,8 @@ class RRset(dns.rdataset.Rdataset):
return dns.rdataset.from_rdata_list(self.ttl, list(self)) return dns.rdataset.from_rdata_list(self.ttl, list(self))
def from_text_list(name, ttl, rdclass, rdtype, text_rdatas): def from_text_list(name, ttl, rdclass, rdtype, text_rdatas,
idna_codec=None):
"""Create an RRset with the specified name, TTL, class, and type, and with """Create an RRset with the specified name, TTL, class, and type, and with
the specified list of rdatas in text format. the specified list of rdatas in text format.
@@ -127,7 +128,7 @@ def from_text_list(name, ttl, rdclass, rdtype, text_rdatas):
""" """
if isinstance(name, string_types): if isinstance(name, string_types):
name = dns.name.from_text(name, None) name = dns.name.from_text(name, None, idna_codec=idna_codec)
if isinstance(rdclass, string_types): if isinstance(rdclass, string_types):
rdclass = dns.rdataclass.from_text(rdclass) rdclass = dns.rdataclass.from_text(rdclass)
if isinstance(rdtype, string_types): if isinstance(rdtype, string_types):
@@ -150,7 +151,7 @@ def from_text(name, ttl, rdclass, rdtype, *text_rdatas):
return from_text_list(name, ttl, rdclass, rdtype, text_rdatas) return from_text_list(name, ttl, rdclass, rdtype, text_rdatas)
def from_rdata_list(name, ttl, rdatas): def from_rdata_list(name, ttl, rdatas, idna_codec=None):
"""Create an RRset with the specified name and TTL, and with """Create an RRset with the specified name and TTL, and with
the specified list of rdata objects. the specified list of rdata objects.
@@ -158,7 +159,7 @@ def from_rdata_list(name, ttl, rdatas):
""" """
if isinstance(name, string_types): if isinstance(name, string_types):
name = dns.name.from_text(name, None) name = dns.name.from_text(name, None, idna_codec=idna_codec)
if len(rdatas) == 0: if len(rdatas) == 0:
raise ValueError("rdata list must not be empty") raise ValueError("rdata list must not be empty")

View File

@@ -232,9 +232,6 @@ class Set(object):
def __delitem__(self, i): def __delitem__(self, i):
del self.items[i] del self.items[i]
def __getslice__(self, i, j):
return self.items[i:j]
def issubset(self, other): def issubset(self, other):
"""Is I{self} a subset of I{other}? """Is I{self} a subset of I{other}?

View File

@@ -15,6 +15,7 @@
"""DNS Wire Data Helper""" """DNS Wire Data Helper"""
import sys
import dns.exception import dns.exception
from ._compat import binary_type, string_types from ._compat import binary_type, string_types
@@ -26,12 +27,16 @@ from ._compat import binary_type, string_types
# out what constant Python will use. # out what constant Python will use.
class _SliceUnspecifiedBound(str): class _SliceUnspecifiedBound(binary_type):
def __getslice__(self, i, j): def __getitem__(self, key):
return j return key.stop
_unspecified_bound = _SliceUnspecifiedBound('')[1:] if sys.version_info < (3,):
def __getslice__(self, i, j): # pylint: disable=getslice-method
return self.__getitem__(slice(i, j))
_unspecified_bound = _SliceUnspecifiedBound()[1:]
class WireData(binary_type): class WireData(binary_type):
@@ -40,26 +45,40 @@ class WireData(binary_type):
def __getitem__(self, key): def __getitem__(self, key):
try: try:
if isinstance(key, slice): if isinstance(key, slice):
return WireData(super(WireData, self).__getitem__(key)) # make sure we are not going outside of valid ranges,
# do stricter control of boundaries than python does
# by default
start = key.start
stop = key.stop
if sys.version_info < (3,):
if stop == _unspecified_bound:
# handle the case where the right bound is unspecified
stop = len(self)
if start < 0 or stop < 0:
raise dns.exception.FormError
# If it's not an empty slice, access left and right bounds
# to make sure they're valid
if start != stop:
super(WireData, self).__getitem__(start)
super(WireData, self).__getitem__(stop - 1)
else:
for index in (start, stop):
if index is None:
continue
elif abs(index) > len(self):
raise dns.exception.FormError
return WireData(super(WireData, self).__getitem__(
slice(start, stop)))
return bytearray(self.unwrap())[key] return bytearray(self.unwrap())[key]
except IndexError: except IndexError:
raise dns.exception.FormError raise dns.exception.FormError
def __getslice__(self, i, j): if sys.version_info < (3,):
try: def __getslice__(self, i, j): # pylint: disable=getslice-method
if j == _unspecified_bound: return self.__getitem__(slice(i, j))
# handle the case where the right bound is unspecified
j = len(self)
if i < 0 or j < 0:
raise dns.exception.FormError
# If it's not an empty slice, access left and right bounds
# to make sure they're valid
if i != j:
super(WireData, self).__getitem__(i)
super(WireData, self).__getitem__(j - 1)
return WireData(super(WireData, self).__getslice__(i, j))
except IndexError:
raise dns.exception.FormError
def __iter__(self): def __iter__(self):
i = 0 i = 0

View File

@@ -19,6 +19,7 @@ from __future__ import generators
import sys import sys
import re import re
import os
from io import BytesIO from io import BytesIO
import dns.exception import dns.exception
@@ -498,18 +499,27 @@ class Zone(object):
@type nl: string or None @type nl: string or None
""" """
str_type = string_types if isinstance(f, string_types):
f = open(f, 'wb')
if nl is None:
opts = 'wb'
else:
opts = 'wb'
if isinstance(f, str_type):
f = open(f, opts)
want_close = True want_close = True
else: else:
want_close = False want_close = False
# must be in this way, f.encoding may contain None, or even attribute
# may not be there
file_enc = getattr(f, 'encoding', None)
if file_enc is None:
file_enc = 'utf-8'
if nl is None:
nl_b = os.linesep.encode(file_enc) # binary mode, '\n' is not enough
nl = u'\n'
elif isinstance(nl, string_types):
nl_b = nl.encode(file_enc)
else:
nl_b = nl
nl = nl.decode()
try: try:
if sorted: if sorted:
names = list(self.keys()) names = list(self.keys())
@@ -520,11 +530,15 @@ class Zone(object):
l = self[n].to_text(n, origin=self.origin, l = self[n].to_text(n, origin=self.origin,
relativize=relativize) relativize=relativize)
if isinstance(l, text_type): if isinstance(l, text_type):
l = l.encode() l_b = l.encode(file_enc)
if nl is None:
f.write(l)
f.write('\n')
else: else:
l_b = l
l = l.decode()
try:
f.write(l_b)
f.write(nl_b)
except TypeError: # textual mode
f.write(l) f.write(l)
f.write(nl) f.write(nl)
finally: finally:
@@ -658,7 +672,7 @@ class _MasterReader(object):
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except dns.exception.SyntaxError: except dns.exception.SyntaxError:
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except: except Exception:
rdclass = self.zone.rdclass rdclass = self.zone.rdclass
if rdclass != self.zone.rdclass: if rdclass != self.zone.rdclass:
raise dns.exception.SyntaxError("RR class is not zone's class") raise dns.exception.SyntaxError("RR class is not zone's class")
@@ -777,7 +791,7 @@ class _MasterReader(object):
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except dns.exception.SyntaxError: except dns.exception.SyntaxError:
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except: except Exception:
rdclass = self.zone.rdclass rdclass = self.zone.rdclass
if rdclass != self.zone.rdclass: if rdclass != self.zone.rdclass:
raise dns.exception.SyntaxError("RR class is not zone's class") raise dns.exception.SyntaxError("RR class is not zone's class")
@@ -787,7 +801,7 @@ class _MasterReader(object):
token = self.tok.get() token = self.tok.get()
if not token.is_identifier(): if not token.is_identifier():
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
except: except Exception:
raise dns.exception.SyntaxError("unknown rdatatype '%s'" % raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
token.value) token.value)