support: upgrade bundled dnspython to 1.16.0 (22e9de1d7957e)

https://github.com/eventlet/eventlet/issues/427
This commit is contained in:
Sergey Shepelev 2017-07-25 12:40:57 +03:00
parent ce72c4378b
commit 2d4fa74645
39 changed files with 2177 additions and 1677 deletions

View File

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

View File

@ -19,6 +19,10 @@ if sys.version_info > (3,):
return x.decode()
def maybe_encode(x):
return x.encode()
def maybe_chr(x):
return x
def maybe_ord(x):
return x
else:
text_type = unicode # pylint: disable=unicode-builtin, undefined-variable
binary_type = str
@ -30,6 +34,10 @@ else:
return x
def maybe_encode(x):
return x
def maybe_chr(x):
return chr(x)
def maybe_ord(x):
return ord(x)
def round_py2_compat(what):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -31,27 +31,40 @@ from ._compat import string_types
class UnsupportedAlgorithm(dns.exception.DNSException):
"""The DNSSEC algorithm is not supported."""
class ValidationFailure(dns.exception.DNSException):
"""The DNSSEC signature is invalid."""
#: RSAMD5
RSAMD5 = 1
#: DH
DH = 2
#: DSA
DSA = 3
#: ECC
ECC = 4
#: RSASHA1
RSASHA1 = 5
#: DSANSEC3SHA1
DSANSEC3SHA1 = 6
#: RSASHA1NSEC3SHA1
RSASHA1NSEC3SHA1 = 7
#: RSASHA256
RSASHA256 = 8
#: RSASHA512
RSASHA512 = 10
#: ECDSAP256SHA256
ECDSAP256SHA256 = 13
#: ECDSAP384SHA384
ECDSAP384SHA384 = 14
#: INDIRECT
INDIRECT = 252
#: PRIVATEDNS
PRIVATEDNS = 253
#: PRIVATEOID
PRIVATEOID = 254
_algorithm_by_text = {
@ -79,8 +92,10 @@ _algorithm_by_value = dict((y, x) for x, y in _algorithm_by_text.items())
def algorithm_from_text(text):
"""Convert text into a DNSSEC algorithm value
@rtype: int"""
"""Convert text into a DNSSEC algorithm value.
Returns an ``int``.
"""
value = _algorithm_by_text.get(text.upper())
if value is None:
@ -90,7 +105,9 @@ def algorithm_from_text(text):
def algorithm_to_text(value):
"""Convert a DNSSEC algorithm value to text
@rtype: string"""
Returns a ``str``.
"""
text = _algorithm_by_value.get(value)
if text is None:
@ -105,6 +122,14 @@ def _to_rdata(record, origin):
def key_id(key, origin=None):
"""Return the key id (a 16-bit number) for the specified key.
Note the *origin* parameter of this function is historical and
is not needed.
Returns an ``int`` between 0 and 65535.
"""
rdata = _to_rdata(key, origin)
rdata = bytearray(rdata)
if key.algorithm == RSAMD5:
@ -121,6 +146,22 @@ def key_id(key, origin=None):
def make_ds(name, key, algorithm, origin=None):
"""Create a DS record for a DNSSEC key.
*name* is the owner name of the DS record.
*key* is a ``dns.rdtypes.ANY.DNSKEY``.
*algorithm* is a string describing which hash algorithm to use. The
currently supported hashes are "SHA1" and "SHA256". Case does not
matter for these strings.
*origin* is a ``dns.name.Name`` and will be used as the origin
if *key* is a relative name.
Returns a ``dns.rdtypes.ANY.DS``.
"""
if algorithm.upper() == 'SHA1':
dsalg = 1
hash = dns.hash.hashes['SHA1']()
@ -232,31 +273,32 @@ def _make_algorithm_id(algorithm):
def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
"""Validate an RRset against a single signature rdata
The owner name of the rrsig is assumed to be the same as the owner name
of the rrset.
The owner name of *rrsig* is assumed to be the same as the owner name
of *rrset*.
@param rrset: The RRset to validate
@type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
tuple
@param rrsig: The signature rdata
@type rrsig: dns.rrset.Rdata
@param keys: The key dictionary.
@type keys: a dictionary keyed by dns.name.Name with node or rdataset
values
@param origin: The origin to use for relative names
@type origin: dns.name.Name or None
@param now: The time to use when validating the signatures. The default
is the current time.
@type now: int
*rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
*rrsig* is a ``dns.rrset.Rdata``, the signature to validate.
*keys* is the key dictionary, used to find the DNSKEY associated with
a given name. The dictionary is keyed by a ``dns.name.Name``, and has
``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
*origin* is a ``dns.name.Name``, the origin to use for relative names.
*now* is an ``int``, the time to use when validating the signatures,
in seconds since the UNIX epoch. The default is the current time.
"""
if isinstance(origin, string_types):
origin = dns.name.from_text(origin, dns.name.root)
for candidate_key in _find_candidate_keys(keys, rrsig):
if not candidate_key:
raise ValidationFailure('unknown key')
candidate_keys = _find_candidate_keys(keys, rrsig)
if candidate_keys is None:
raise ValidationFailure('unknown key')
for candidate_key in candidate_keys:
# For convenience, allow the rrset to be specified as a (name,
# rdataset) tuple as well as a proper rrset
if isinstance(rrset, tuple):
@ -322,7 +364,8 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
keyptr = candidate_key.key
x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len])
y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2])
assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)
if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y):
raise ValidationFailure('invalid ECDSA key')
point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,
curve)
@ -374,22 +417,22 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
def _validate(rrset, rrsigset, keys, origin=None, now=None):
"""Validate an RRset
"""Validate an RRset.
@param rrset: The RRset to validate
@type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
tuple
@param rrsigset: The signature RRset
@type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
tuple
@param keys: The key dictionary.
@type keys: a dictionary keyed by dns.name.Name with node or rdataset
values
@param origin: The origin to use for relative names
@type origin: dns.name.Name or None
@param now: The time to use when validating the signatures. The default
is the current time.
@type now: int
*rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
*rrsigset* is the signature RRset to be validated. It can be a
``dns.rrset.RRset`` or a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
*keys* is the key dictionary, used to find the DNSKEY associated with
a given name. The dictionary is keyed by a ``dns.name.Name``, and has
``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
*origin* is a ``dns.name.Name``, the origin to use for relative names.
*now* is an ``int``, the time to use when validating the signatures,
in seconds since the UNIX epoch. The default is the current time.
"""
if isinstance(origin, string_types):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2006, 2007, 2009, 2011 Nominum, Inc.
# Copyright (C) 2006-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,31 +13,32 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS E.164 helpers
@var public_enum_domain: The DNS public ENUM domain, e164.arpa.
@type public_enum_domain: dns.name.Name object
"""
"""DNS E.164 helpers."""
import dns.exception
import dns.name
import dns.resolver
from ._compat import string_types
from ._compat import string_types, maybe_decode
#: The public E.164 domain.
public_enum_domain = dns.name.from_text('e164.arpa.')
def from_e164(text, origin=public_enum_domain):
"""Convert an E.164 number in textual form into a Name object whose
value is the ENUM domain name for that number.
@param text: an E.164 number in textual form.
@type text: str
@param origin: The domain in which the number should be constructed.
The default is e164.arpa.
@type origin: dns.name.Name object or None
@rtype: dns.name.Name object
Non-digits in the text are ignored, i.e. "16505551212",
"+1.650.555.1212" and "1 (650) 555-1212" are all the same.
*text*, a ``text``, is an E.164 number in textual form.
*origin*, a ``dns.name.Name``, the domain in which the number
should be constructed. The default is ``e164.arpa.``.
Returns a ``dns.name.Name``.
"""
parts = [d for d in text if d.isdigit()]
parts.reverse()
return dns.name.from_text('.'.join(parts), origin=origin)
@ -45,14 +46,23 @@ def from_e164(text, origin=public_enum_domain):
def to_e164(name, origin=public_enum_domain, want_plus_prefix=True):
"""Convert an ENUM domain name into an E.164 number.
@param name: the ENUM domain name.
@type name: dns.name.Name object.
@param origin: A domain containing the ENUM domain name. The
name is relativized to this domain before being converted to text.
@type origin: dns.name.Name object or None
@param want_plus_prefix: if True, add a '+' to the beginning of the
returned number.
@rtype: str
Note that dnspython does not have any information about preferred
number formats within national numbering plans, so all numbers are
emitted as a simple string of digits, prefixed by a '+' (unless
*want_plus_prefix* is ``False``).
*name* is a ``dns.name.Name``, the ENUM domain name.
*origin* is a ``dns.name.Name``, a domain containing the ENUM
domain name. The name is relativized to this domain before being
converted to text. If ``None``, no relativization is done.
*want_plus_prefix* is a ``bool``. If True, add a '+' to the beginning of
the returned number.
Returns a ``text``.
"""
if origin is not None:
name = name.relativize(origin)
@ -63,14 +73,22 @@ def to_e164(name, origin=public_enum_domain, want_plus_prefix=True):
text = b''.join(dlabels)
if want_plus_prefix:
text = b'+' + text
return text
return maybe_decode(text)
def query(number, domains, resolver=None):
"""Look for NAPTR RRs for the specified number in the specified domains.
e.g. lookup('16505551212', ['e164.dnspython.org.', 'e164.arpa.'])
*number*, a ``text`` is the number to look for.
*domains* is an iterable containing ``dns.name.Name`` values.
*resolver*, a ``dns.resolver.Resolver``, is the resolver to use. If
``None``, the default resolver is used.
"""
if resolver is None:
resolver = dns.resolver.get_default_resolver()
e_nx = dns.resolver.NXDOMAIN()

View File

@ -1,4 +1,4 @@
# Copyright (C) 2009, 2011 Nominum, Inc.
# Copyright (C) 2009-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -15,18 +15,42 @@
"""EDNS Options"""
NSID = 3
from __future__ import absolute_import
import math
import struct
import dns.inet
#: NSID
NSID = 3
#: DAU
DAU = 5
#: DHU
DHU = 6
#: N3U
N3U = 7
#: ECS (client-subnet)
ECS = 8
#: EXPIRE
EXPIRE = 9
#: COOKIE
COOKIE = 10
#: KEEPALIVE
KEEPALIVE = 11
#: PADDING
PADDING = 12
#: CHAIN
CHAIN = 13
class Option(object):
"""Base class for all EDNS option types.
"""
"""Base class for all EDNS option types."""
def __init__(self, otype):
"""Initialize an option.
@param otype: The rdata type
@type otype: int
*otype*, an ``int``, is the option type.
"""
self.otype = otype
@ -37,23 +61,26 @@ class Option(object):
@classmethod
def from_wire(cls, otype, wire, current, olen):
"""Build an EDNS option object from wire format
"""Build an EDNS option object from wire format.
*otype*, an ``int``, is the option type.
*wire*, a ``binary``, is the wire-format message.
*current*, an ``int``, is the offset in *wire* of the beginning
of the rdata.
*olen*, an ``int``, is the length of the wire-format option data
Returns a ``dns.edns.Option``.
"""
@param otype: The option type
@type otype: int
@param wire: The wire-format message
@type wire: string
@param current: The offset in wire of the beginning of the rdata.
@type current: int
@param olen: The length of the wire-format option data
@type olen: int
@rtype: dns.edns.Option instance"""
raise NotImplementedError
def _cmp(self, other):
"""Compare an EDNS option with another option of the same type.
Return < 0 if self < other, 0 if self == other,
and > 0 if self > other.
Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.
"""
raise NotImplementedError
@ -98,7 +125,7 @@ class Option(object):
class GenericOption(Option):
"""Generate Rdata Class
"""Generic Option Class
This class is used for EDNS option types for which we have no better
implementation.
@ -111,6 +138,9 @@ class GenericOption(Option):
def to_wire(self, file):
file.write(self.data)
def to_text(self):
return "Generic %d" % self.otype
@classmethod
def from_wire(cls, otype, wire, current, olen):
return cls(otype, wire[current: current + olen])
@ -122,11 +152,96 @@ class GenericOption(Option):
return 1
return -1
class ECSOption(Option):
"""EDNS Client Subnet (ECS, RFC7871)"""
def __init__(self, address, srclen=None, scopelen=0):
"""*address*, a ``text``, is the client address information.
*srclen*, an ``int``, the source prefix length, which is the
leftmost number of bits of the address to be used for the
lookup. The default is 24 for IPv4 and 56 for IPv6.
*scopelen*, an ``int``, the scope prefix length. This value
must be 0 in queries, and should be set in responses.
"""
super(ECSOption, self).__init__(ECS)
af = dns.inet.af_for_address(address)
if af == dns.inet.AF_INET6:
self.family = 2
if srclen is None:
srclen = 56
elif af == dns.inet.AF_INET:
self.family = 1
if srclen is None:
srclen = 24
else:
raise ValueError('Bad ip family')
self.address = address
self.srclen = srclen
self.scopelen = scopelen
addrdata = dns.inet.inet_pton(af, address)
nbytes = int(math.ceil(srclen/8.0))
# Truncate to srclen and pad to the end of the last octet needed
# See RFC section 6
self.addrdata = addrdata[:nbytes]
nbits = srclen % 8
if nbits != 0:
last = struct.pack('B', ord(self.addrdata[-1:]) & (0xff << nbits))
self.addrdata = self.addrdata[:-1] + last
def to_text(self):
return "ECS %s/%s scope/%s" % (self.address, self.srclen,
self.scopelen)
def to_wire(self, file):
file.write(struct.pack('!H', self.family))
file.write(struct.pack('!BB', self.srclen, self.scopelen))
file.write(self.addrdata)
@classmethod
def from_wire(cls, otype, wire, cur, olen):
family, src, scope = struct.unpack('!HBB', wire[cur:cur+4])
cur += 4
addrlen = int(math.ceil(src/8.0))
if family == 1:
af = dns.inet.AF_INET
pad = 4 - addrlen
elif family == 2:
af = dns.inet.AF_INET6
pad = 16 - addrlen
else:
raise ValueError('unsupported family')
addr = dns.inet.inet_ntop(af, wire[cur:cur+addrlen] + b'\x00' * pad)
return cls(addr, src, scope)
def _cmp(self, other):
if self.addrdata == other.addrdata:
return 0
if self.addrdata > other.addrdata:
return 1
return -1
_type_to_class = {
ECS: ECSOption
}
def get_option_class(otype):
"""Return the class for the specified option type.
The GenericOption class is used if a more specific class is not
known.
"""
cls = _type_to_class.get(otype)
if cls is None:
cls = GenericOption
@ -134,17 +249,19 @@ def get_option_class(otype):
def option_from_wire(otype, wire, current, olen):
"""Build an EDNS option object from wire format
"""Build an EDNS option object from wire format.
@param otype: The option type
@type otype: int
@param wire: The wire-format message
@type wire: string
@param current: The offset in wire of the beginning of the rdata.
@type current: int
@param olen: The length of the wire-format option data
@type olen: int
@rtype: dns.edns.Option instance"""
*otype*, an ``int``, is the option type.
*wire*, a ``binary``, is the wire-format message.
*current*, an ``int``, is the offset in *wire* of the beginning
of the rdata.
*olen*, an ``int``, is the length of the wire-format option data
Returns an instance of a subclass of ``dns.edns.Option``.
"""
cls = get_option_class(otype)
return cls.from_wire(otype, wire, current, olen)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2009, 2011 Nominum, Inc.
# Copyright (C) 2009-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -25,6 +25,11 @@ except ImportError:
class EntropyPool(object):
# This is an entropy pool for Python implementations that do not
# have a working SystemRandom. I'm not sure there are any, but
# leaving this code doesn't hurt anything as the library code
# is used if present.
def __init__(self, seed=None):
self.pool_index = 0
self.digest = None

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,32 +13,35 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Common DNS Exceptions."""
"""Common DNS Exceptions.
Dnspython modules may also define their own exceptions, which will
always be subclasses of ``DNSException``.
"""
class DNSException(Exception):
"""Abstract base class shared by all dnspython exceptions.
It supports two basic modes of operation:
a) Old/compatible mode is used if __init__ was called with
empty **kwargs.
In compatible mode all *args are passed to standard Python Exception class
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()
if *args is empty.
a) Old/compatible mode is used if ``__init__`` was called with
empty *kwargs*. In compatible mode all *args* are passed
to the standard Python Exception class as before and all *args* are
printed by the standard ``__str__`` implementation. Class variable
``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()``
if *args* is empty.
b) New/parametrized mode is used if __init__ was called with
non-empty **kwargs.
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
self.kwargs and used in new __str__ implementation to construct
formatted message based on self.fmt string.
b) New/parametrized mode is used if ``__init__`` was called with
non-empty *kwargs*.
In the new mode *args* must be empty and all kwargs must match
those set in class variable ``supp_kwargs``. All kwargs are stored inside
``self.kwargs`` and used in a new ``__str__`` implementation to construct
a formatted message based on the ``fmt`` class variable, a ``string``.
In the simplest case it is enough to override supp_kwargs and fmt
class variables to get nice parametrized messages.
In the simplest case it is enough to override the ``supp_kwargs``
and ``fmt`` class variables to get nice parametrized messages.
"""
msg = None # non-parametrized message
supp_kwargs = set() # accepted parameters for _fmt_kwargs (sanity check)
fmt = None # message parametrized with results from _fmt_kwargs
@ -102,27 +105,22 @@ class DNSException(Exception):
class FormError(DNSException):
"""DNS message is malformed."""
class SyntaxError(DNSException):
"""Text input is malformed."""
class UnexpectedEnd(SyntaxError):
"""Text input ended unexpectedly."""
class TooBig(DNSException):
"""The DNS message is too big."""
class Timeout(DNSException):
"""The DNS operation timed out."""
supp_kwargs = set(['timeout'])
fmt = "The DNS operation timed out after {timeout} seconds"

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -17,16 +17,24 @@
# Standard DNS flags
#: Query Response
QR = 0x8000
#: Authoritative Answer
AA = 0x0400
#: Truncated Response
TC = 0x0200
#: Recursion Desired
RD = 0x0100
#: Recursion Available
RA = 0x0080
#: Authentic Data
AD = 0x0020
#: Checking Disabled
CD = 0x0010
# EDNS flags
#: DNSSEC answer OK
DO = 0x8000
_by_text = {
@ -83,7 +91,9 @@ def _to_text(flags, table, order):
def from_text(text):
"""Convert a space-separated list of flag text values into a flags
value.
@rtype: int"""
Returns an ``int``
"""
return _from_text(text, _by_text)
@ -91,7 +101,9 @@ def from_text(text):
def to_text(flags):
"""Convert a flags value into a space-separated list of flag text
values.
@rtype: string"""
Returns a ``text``.
"""
return _to_text(flags, _by_value, _flags_order)
@ -99,7 +111,9 @@ def to_text(flags):
def edns_from_text(text):
"""Convert a space-separated list of EDNS flag text values into a EDNS
flags value.
@rtype: int"""
Returns an ``int``
"""
return _from_text(text, _edns_by_text)
@ -107,6 +121,8 @@ def edns_from_text(text):
def edns_to_text(flags):
"""Convert an EDNS flags value into a space-separated list of EDNS flag
text values.
@rtype: string"""
Returns a ``text``.
"""
return _to_text(flags, _edns_by_value, _edns_flags_order)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2012-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -17,18 +17,16 @@
import dns
def from_text(text):
"""Convert the text form of a range in a GENERATE statement to an
"""Convert the text form of a range in a ``$GENERATE`` statement to an
integer.
@param text: the textual range
@type text: string
@return: The start, stop and step values.
@rtype: tuple
"""
# TODO, figure out the bounds on start, stop and step.
*text*, a ``str``, the textual range in ``$GENERATE`` form.
Returns a tuple of three ``int`` values ``(start, stop, step)``.
"""
# TODO, figure out the bounds on start, stop and step.
step = 1
cur = ''
state = 0

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -20,6 +20,7 @@ import socket
import dns.ipv4
import dns.ipv6
from ._compat import maybe_ord
# We assume that AF_INET is always defined.
@ -38,13 +39,14 @@ except AttributeError:
def inet_pton(family, text):
"""Convert the textual form of a network address into its binary form.
@param family: the address family
@type family: int
@param text: the textual address
@type text: string
@raises NotImplementedError: the address family specified is not
*family* is an ``int``, the address family.
*text* is a ``text``, the textual address.
Raises ``NotImplementedError`` if the address family specified is not
implemented.
@rtype: string
Returns a ``binary``.
"""
if family == AF_INET:
@ -58,14 +60,16 @@ def inet_pton(family, text):
def inet_ntop(family, address):
"""Convert the binary form of a network address into its textual form.
@param family: the address family
@type family: int
@param address: the binary address
@type address: string
@raises NotImplementedError: the address family specified is not
*family* is an ``int``, the address family.
*address* is a ``binary``, the network address in binary form.
Raises ``NotImplementedError`` if the address family specified is not
implemented.
@rtype: string
Returns a ``text``.
"""
if family == AF_INET:
return dns.ipv4.inet_ntoa(address)
elif family == AF_INET6:
@ -77,11 +81,14 @@ def inet_ntop(family, address):
def af_for_address(text):
"""Determine the address family of a textual-form network address.
@param text: the textual address
@type text: string
@raises ValueError: the address family cannot be determined from the input.
@rtype: int
*text*, a ``text``, the textual address.
Raises ``ValueError`` if the address family cannot be determined
from the input.
Returns an ``int``.
"""
try:
dns.ipv4.inet_aton(text)
return AF_INET
@ -96,16 +103,20 @@ def af_for_address(text):
def is_multicast(text):
"""Is the textual-form network address a multicast address?
@param text: the textual address
@raises ValueError: the address family cannot be determined from the input.
@rtype: bool
*text*, a ``text``, the textual address.
Raises ``ValueError`` if the address family cannot be determined
from the input.
Returns a ``bool``.
"""
try:
first = ord(dns.ipv4.inet_aton(text)[0])
first = maybe_ord(dns.ipv4.inet_aton(text)[0])
return first >= 224 and first <= 239
except Exception:
try:
first = ord(dns.ipv6.inet_aton(text)[0])
first = maybe_ord(dns.ipv6.inet_aton(text)[0])
return first == 255
except Exception:
raise ValueError

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -21,26 +21,28 @@ import dns.exception
from ._compat import binary_type
def inet_ntoa(address):
"""Convert an IPv4 address in network form to text form.
"""Convert an IPv4 address in binary form to text form.
@param address: The IPv4 address
@type address: string
@returns: string
*address*, a ``binary``, the IPv4 address in binary form.
Returns a ``text``.
"""
if len(address) != 4:
raise dns.exception.SyntaxError
if not isinstance(address, bytearray):
address = bytearray(address)
return (u'%u.%u.%u.%u' % (address[0], address[1],
address[2], address[3])).encode()
return ('%u.%u.%u.%u' % (address[0], address[1],
address[2], address[3]))
def inet_aton(text):
"""Convert an IPv4 address in text form to network form.
"""Convert an IPv4 address in text form to binary form.
@param text: The IPv4 address
@type text: string
@returns: string
*text*, a ``text``, the IPv4 address in textual form.
Returns a ``binary``.
"""
if not isinstance(text, binary_type):
text = text.encode()
parts = text.split(b'.')

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -22,15 +22,15 @@ import dns.exception
import dns.ipv4
from ._compat import xrange, binary_type, maybe_decode
_leading_zero = re.compile(b'0+([0-9a-f]+)')
_leading_zero = re.compile('0+([0-9a-f]+)')
def inet_ntoa(address):
"""Convert a network format IPv6 address into text.
"""Convert an IPv6 address in binary form to text form.
@param address: the binary address
@type address: string
@rtype: string
@raises ValueError: the address isn't 16 bytes long
*address*, a ``binary``, the IPv6 address in binary form.
Raises ``ValueError`` if the address isn't 16 bytes long.
Returns a ``text``.
"""
if len(address) != 16:
@ -40,7 +40,7 @@ def inet_ntoa(address):
i = 0
l = len(hex)
while i < l:
chunk = hex[i : i + 4]
chunk = maybe_decode(hex[i : i + 4])
# strip leading zeros. we do this with an re instead of
# with lstrip() because lstrip() didn't support chars until
# python 2.2.2
@ -57,7 +57,7 @@ def inet_ntoa(address):
start = -1
last_was_zero = False
for i in xrange(8):
if chunks[i] != b'0':
if chunks[i] != '0':
if last_was_zero:
end = i
current_len = end - start
@ -77,31 +77,30 @@ def inet_ntoa(address):
if best_len > 1:
if best_start == 0 and \
(best_len == 6 or
best_len == 5 and chunks[5] == b'ffff'):
best_len == 5 and chunks[5] == 'ffff'):
# We have an embedded IPv4 address
if best_len == 6:
prefix = b'::'
prefix = '::'
else:
prefix = b'::ffff:'
prefix = '::ffff:'
hex = prefix + dns.ipv4.inet_ntoa(address[12:])
else:
hex = b':'.join(chunks[:best_start]) + b'::' + \
b':'.join(chunks[best_start + best_len:])
hex = ':'.join(chunks[:best_start]) + '::' + \
':'.join(chunks[best_start + best_len:])
else:
hex = b':'.join(chunks)
return maybe_decode(hex)
hex = ':'.join(chunks)
return hex
_v4_ending = re.compile(b'(.*):(\d+\.\d+\.\d+\.\d+)$')
_colon_colon_start = re.compile(b'::.*')
_colon_colon_end = re.compile(b'.*::$')
def inet_aton(text):
"""Convert a text format IPv6 address into network format.
"""Convert an IPv6 address in text form to binary form.
@param text: the textual address
@type text: string
@rtype: string
@raises dns.exception.SyntaxError: the text was not properly formatted
*text*, a ``text``, the IPv6 address in textual form.
Returns a ``binary``.
"""
#
@ -169,4 +168,11 @@ def inet_aton(text):
_mapped_prefix = b'\x00' * 10 + b'\xff\xff'
def is_mapped(address):
"""Is the specified address a mapped IPv4 address?
*address*, a ``binary`` is an IPv6 address in binary form.
Returns a ``bool``.
"""
return address.startswith(_mapped_prefix)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -40,114 +40,46 @@ from ._compat import long, xrange, string_types
class ShortHeader(dns.exception.FormError):
"""The DNS packet passed to from_wire() is too short."""
class TrailingJunk(dns.exception.FormError):
"""The DNS packet passed to from_wire() has extra junk at the end of it."""
class UnknownHeaderField(dns.exception.DNSException):
"""The header field name was not recognized when converting from text
into a message."""
class BadEDNS(dns.exception.FormError):
"""OPT record occurred somewhere other than the start of
"""An OPT record occurred somewhere other than the start of
the additional data section."""
class BadTSIG(dns.exception.FormError):
"""A TSIG record occurred somewhere other than the end of
the additional data section."""
class UnknownTSIGKey(dns.exception.DNSException):
"""A TSIG with an unknown key was received."""
#: The question section number
QUESTION = 0
#: The answer section number
ANSWER = 1
#: The authority section number
AUTHORITY = 2
#: The additional section number
ADDITIONAL = 3
class Message(object):
"""A DNS message.
@ivar id: The query id; the default is a randomly chosen id.
@type id: int
@ivar flags: The DNS flags of the message. @see: RFC 1035 for an
explanation of these flags.
@type flags: int
@ivar question: The question section.
@type question: list of dns.rrset.RRset objects
@ivar answer: The answer section.
@type answer: list of dns.rrset.RRset objects
@ivar authority: The authority section.
@type authority: list of dns.rrset.RRset objects
@ivar additional: The additional data section.
@type additional: list of dns.rrset.RRset objects
@ivar edns: The EDNS level to use. The default is -1, no Edns.
@type edns: int
@ivar ednsflags: The EDNS flags
@type ednsflags: long
@ivar payload: The EDNS payload size. The default is 0.
@type payload: int
@ivar options: The EDNS options
@type options: list of dns.edns.Option objects
@ivar request_payload: The associated request's EDNS payload size.
@type request_payload: int
@ivar keyring: The TSIG keyring to use. The default is None.
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: dns.name.Name object
@ivar keyalgorithm: The TSIG algorithm to use; defaults to
dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
in dns.tsig, and the currently implemented algorithms are
HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
HMAC_SHA512.
@type keyalgorithm: string
@ivar request_mac: The TSIG MAC of the request message associated with
this message; used when validating TSIG signatures. @see: RFC 2845 for
more information on TSIG fields.
@type request_mac: string
@ivar fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@ivar original_id: TSIG original id; defaults to the message's id
@type original_id: int
@ivar tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@ivar other_data: TSIG other data.
@type other_data: string
@ivar mac: The TSIG MAC for this message.
@type mac: string
@ivar xfr: Is the message being used to contain the results of a DNS
zone transfer? The default is False.
@type xfr: bool
@ivar origin: The origin of the zone in messages which are used for
zone transfers or for DNS dynamic updates. The default is None.
@type origin: dns.name.Name object
@ivar tsig_ctx: The TSIG signature context associated with this
message. The default is None.
@type tsig_ctx: hmac.HMAC object
@ivar had_tsig: Did the message decoded from wire format have a TSIG
signature?
@type had_tsig: bool
@ivar multi: Is this message part of a multi-message sequence? The
default is false. This variable is used when validating TSIG signatures
on messages which are part of a zone transfer.
@type multi: bool
@ivar first: Is this message standalone, or the first of a multi
message sequence? This variable is used when validating TSIG signatures
on messages which are part of a zone transfer.
@type first: bool
@ivar index: An index of rrsets in the message. The index key is
(section, name, rdclass, rdtype, covers, deleting). Indexing can be
disabled by setting the index to None.
@type index: dict
"""
"""A DNS message."""
def __init__(self, id=None):
if id is None:
@ -167,12 +99,12 @@ class Message(object):
self.keyring = None
self.keyname = None
self.keyalgorithm = dns.tsig.default_algorithm
self.request_mac = ''
self.other_data = ''
self.request_mac = b''
self.other_data = b''
self.tsig_error = 0
self.fudge = 300
self.original_id = self.id
self.mac = ''
self.mac = b''
self.xfr = False
self.origin = None
self.tsig_ctx = None
@ -190,10 +122,10 @@ class Message(object):
def to_text(self, origin=None, relativize=True, **kw):
"""Convert the message to text.
The I{origin}, I{relativize}, and any other keyword
arguments are passed to the rrset to_wire() method.
The *origin*, *relativize*, and any other keyword
arguments are passed to the RRset ``to_wire()`` method.
@rtype: string
Returns a ``text``.
"""
s = StringIO()
@ -209,6 +141,8 @@ class Message(object):
s.write(u'eflags %s\n' %
dns.flags.edns_to_text(self.ednsflags))
s.write(u'payload %d\n' % self.payload)
for opt in self.options:
s.write(u'option %s\n' % opt.to_text())
is_update = dns.opcode.is_update(self.flags)
if is_update:
s.write(u';ZONE\n')
@ -245,7 +179,10 @@ class Message(object):
def __eq__(self, other):
"""Two messages are equal if they have the same content in the
header, question, answer, and authority sections.
@rtype: bool"""
Returns a ``bool``.
"""
if not isinstance(other, Message):
return False
if self.id != other.id:
@ -273,13 +210,14 @@ class Message(object):
return True
def __ne__(self, other):
"""Are two messages not equal?
@rtype: bool"""
return not self.__eq__(other)
def is_response(self, other):
"""Is other a response to self?
@rtype: bool"""
"""Is this message a response to *other*?
Returns a ``bool``.
"""
if other.flags & dns.flags.QR == 0 or \
self.id != other.id or \
dns.opcode.from_flags(self.flags) != \
@ -299,14 +237,48 @@ class Message(object):
return True
def section_number(self, section):
"""Return the "section number" of the specified section for use
in indexing. The question section is 0, the answer section is 1,
the authority section is 2, and the additional section is 3.
*section* is one of the section attributes of this message.
Raises ``ValueError`` if the section isn't known.
Returns an ``int``.
"""
if section is self.question:
return 0
return QUESTION
elif section is self.answer:
return 1
return ANSWER
elif section is self.authority:
return 2
return AUTHORITY
elif section is self.additional:
return 3
return ADDITIONAL
else:
raise ValueError('unknown section')
def section_from_number(self, number):
"""Return the "section number" of the specified section for use
in indexing. The question section is 0, the answer section is 1,
the authority section is 2, and the additional section is 3.
*section* is one of the section attributes of this message.
Raises ``ValueError`` if the section isn't known.
Returns an ``int``.
"""
if number == QUESTION:
return self.question
elif number == ANSWER:
return self.answer
elif number == AUTHORITY:
return self.authority
elif number == ADDITIONAL:
return self.additional
else:
raise ValueError('unknown section')
@ -315,30 +287,45 @@ class Message(object):
force_unique=False):
"""Find the RRset with the given attributes in the specified section.
@param section: the section of the message to look in, e.g.
self.answer.
@type section: list of dns.rrset.RRset objects
@param name: the name of the RRset
@type name: dns.name.Name object
@param rdclass: the class of the RRset
@type rdclass: int
@param rdtype: the type of the RRset
@type rdtype: int
@param covers: the covers value of the RRset
@type covers: int
@param deleting: the deleting value of the RRset
@type deleting: int
@param create: If True, create the RRset if it is not found.
The created RRset is appended to I{section}.
@type create: bool
@param force_unique: If True and create is also True, create a
new RRset regardless of whether a matching RRset exists already.
@type force_unique: bool
@raises KeyError: the RRset was not found and create was False
@rtype: dns.rrset.RRset object"""
*section*, an ``int`` section number, or one of the section
attributes of this message. This specifies the
the section of the message to search. For example::
key = (self.section_number(section),
name, rdclass, rdtype, covers, deleting)
my_message.find_rrset(my_message.answer, name, rdclass, rdtype)
my_message.find_rrset(dns.message.ANSWER, name, rdclass, rdtype)
*name*, a ``dns.name.Name``, the name of the RRset.
*rdclass*, an ``int``, the class of the RRset.
*rdtype*, an ``int``, the type of the RRset.
*covers*, an ``int`` or ``None``, the covers value of the RRset.
The default is ``None``.
*deleting*, an ``int`` or ``None``, the deleting value of the RRset.
The default is ``None``.
*create*, a ``bool``. If ``True``, create the RRset if it is not found.
The created RRset is appended to *section*.
*force_unique*, a ``bool``. If ``True`` and *create* is also ``True``,
create a new RRset regardless of whether a matching RRset exists
already. The default is ``False``. This is useful when creating
DDNS Update messages, as order matters for them.
Raises ``KeyError`` if the RRset was not found and create was
``False``.
Returns a ``dns.rrset.RRset object``.
"""
if isinstance(section, int):
section_number = section
section = self.section_from_number(section_number)
else:
section_number = self.section_number(section)
key = (section_number, name, rdclass, rdtype, covers, deleting)
if not force_unique:
if self.index is not None:
rrset = self.index.get(key)
@ -363,26 +350,35 @@ class Message(object):
If the RRset is not found, None is returned.
@param section: the section of the message to look in, e.g.
self.answer.
@type section: list of dns.rrset.RRset objects
@param name: the name of the RRset
@type name: dns.name.Name object
@param rdclass: the class of the RRset
@type rdclass: int
@param rdtype: the type of the RRset
@type rdtype: int
@param covers: the covers value of the RRset
@type covers: int
@param deleting: the deleting value of the RRset
@type deleting: int
@param create: If True, create the RRset if it is not found.
The created RRset is appended to I{section}.
@type create: bool
@param force_unique: If True and create is also True, create a
new RRset regardless of whether a matching RRset exists already.
@type force_unique: bool
@rtype: dns.rrset.RRset object or None"""
*section*, an ``int`` section number, or one of the section
attributes of this message. This specifies the
the section of the message to search. For example::
my_message.get_rrset(my_message.answer, name, rdclass, rdtype)
my_message.get_rrset(dns.message.ANSWER, name, rdclass, rdtype)
*name*, a ``dns.name.Name``, the name of the RRset.
*rdclass*, an ``int``, the class of the RRset.
*rdtype*, an ``int``, the type of the RRset.
*covers*, an ``int`` or ``None``, the covers value of the RRset.
The default is ``None``.
*deleting*, an ``int`` or ``None``, the deleting value of the RRset.
The default is ``None``.
*create*, a ``bool``. If ``True``, create the RRset if it is not found.
The created RRset is appended to *section*.
*force_unique*, a ``bool``. If ``True`` and *create* is also ``True``,
create a new RRset regardless of whether a matching RRset exists
already. The default is ``False``. This is useful when creating
DDNS Update messages, as order matters for them.
Returns a ``dns.rrset.RRset object`` or ``None``.
"""
try:
rrset = self.find_rrset(section, name, rdclass, rdtype, covers,
@ -395,17 +391,19 @@ class Message(object):
"""Return a string containing the message in DNS compressed wire
format.
Additional keyword arguments are passed to the rrset to_wire()
Additional keyword arguments are passed to the RRset ``to_wire()``
method.
@param origin: The origin to be appended to any relative names.
@type origin: dns.name.Name object
@param max_size: The maximum size of the wire format output; default
is 0, which means 'the message's request payload, if nonzero, or
65536'.
@type max_size: int
@raises dns.exception.TooBig: max_size was exceeded
@rtype: string
*origin*, a ``dns.name.Name`` or ``None``, the origin to be appended
to any relative names.
*max_size*, an ``int``, the maximum size of the wire format
output; default is 0, which means "the message's request
payload, if nonzero, or 65535".
Raises ``dns.exception.TooBig`` if *max_size* was exceeded.
Returns a ``binary``.
"""
if max_size == 0:
@ -438,30 +436,34 @@ class Message(object):
return r.get_wire()
def use_tsig(self, keyring, keyname=None, fudge=300,
original_id=None, tsig_error=0, other_data='',
original_id=None, tsig_error=0, other_data=b'',
algorithm=dns.tsig.default_algorithm):
"""When sending, a TSIG signature using the specified keyring
and keyname should be added.
@param keyring: The TSIG keyring to use; defaults to None.
@type keyring: dict
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key.
@type keyname: dns.name.Name or string
@param fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@param original_id: TSIG original id; defaults to the message's id
@type original_id: int
@param tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@param other_data: TSIG other data.
@type other_data: string
@param algorithm: The TSIG algorithm to use; defaults to
dns.tsig.default_algorithm
See the documentation of the Message class for a complete
description of the keyring dictionary.
*keyring*, a ``dict``, the TSIG keyring to use. If a
*keyring* is specified but a *keyname* is not, then the key
used will be the first key in the *keyring*. Note that the
order of keys in a dictionary is not defined, so applications
should supply a keyname when a keyring is used, unless they
know the keyring contains only one key.
*keyname*, a ``dns.name.Name`` or ``None``, the name of the TSIG key
to use; defaults to ``None``. The key must be defined in the keyring.
*fudge*, an ``int``, the TSIG time fudge.
*original_id*, an ``int``, the TSIG original id. If ``None``,
the message's id is used.
*tsig_error*, an ``int``, the TSIG error code.
*other_data*, a ``binary``, the TSIG other data.
*algorithm*, a ``dns.name.Name``, the TSIG algorithm to use.
"""
self.keyring = keyring
@ -483,23 +485,26 @@ class Message(object):
def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None,
options=None):
"""Configure EDNS behavior.
@param edns: The EDNS level to use. Specifying None, False, or -1
means 'do not use EDNS', and in this case the other parameters are
ignored. Specifying True is equivalent to specifying 0, i.e. 'use
EDNS0'.
@type edns: int or bool or None
@param ednsflags: EDNS flag values.
@type ednsflags: int
@param payload: The EDNS sender's payload field, which is the maximum
size of UDP datagram the sender can handle.
@type payload: int
@param request_payload: The EDNS payload size to use when sending
this message. If not specified, defaults to the value of payload.
@type request_payload: int or None
@param options: The EDNS options
@type options: None or list of dns.edns.Option objects
@see: RFC 2671
*edns*, an ``int``, is the EDNS level to use. Specifying
``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case
the other parameters are ignored. Specifying ``True`` is
equivalent to specifying 0, i.e. "use EDNS0".
*ednsflags*, an ``int``, the EDNS flag values.
*payload*, an ``int``, is the EDNS sender's payload field, which is the
maximum size of UDP datagram the sender can handle. I.e. how big
a response to this message can be.
*request_payload*, an ``int``, is the EDNS payload size to use when
sending this message. If not specified, defaults to the value of
*payload*.
*options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS
options.
"""
if edns is None or edns is False:
edns = -1
if edns is True:
@ -525,11 +530,13 @@ class Message(object):
def want_dnssec(self, wanted=True):
"""Enable or disable 'DNSSEC desired' flag in requests.
@param wanted: Is DNSSEC desired? If True, EDNS is enabled if
required, and then the DO bit is set. If False, the DO bit is
cleared if EDNS is enabled.
@type wanted: bool
*wanted*, a ``bool``. If ``True``, then DNSSEC data is
desired in the response, EDNS is enabled if required, and then
the DO bit is set. If ``False``, the DO bit is cleared if
EDNS is enabled.
"""
if wanted:
if self.edns < 0:
self.use_edns()
@ -539,14 +546,15 @@ class Message(object):
def rcode(self):
"""Return the rcode.
@rtype: int
Returns an ``int``.
"""
return dns.rcode.from_flags(self.flags, self.ednsflags)
def set_rcode(self, rcode):
"""Set the rcode.
@param rcode: the rcode
@type rcode: int
*rcode*, an ``int``, is the rcode to set.
"""
(value, evalue) = dns.rcode.to_flags(rcode)
self.flags &= 0xFFF0
@ -558,14 +566,15 @@ class Message(object):
def opcode(self):
"""Return the opcode.
@rtype: int
Returns an ``int``.
"""
return dns.opcode.from_flags(self.flags)
def set_opcode(self, opcode):
"""Set the opcode.
@param opcode: the opcode
@type opcode: int
*opcode*, an ``int``, is the opcode to set.
"""
self.flags &= 0x87FF
self.flags |= dns.opcode.to_flags(opcode)
@ -575,23 +584,16 @@ class _WireReader(object):
"""Wire format reader.
@ivar wire: the wire-format message.
@type wire: string
@ivar message: The message object being built
@type message: dns.message.Message object
@ivar current: When building a message object from wire format, this
wire: a binary, is the wire-format message.
message: The message object being built
current: When building a message object from wire format, this
variable contains the offset from the beginning of wire of the next octet
to be read.
@type current: int
@ivar updating: Is the message a dynamic update?
@type updating: bool
@ivar one_rr_per_rrset: Put each RR into its own RRset?
@type one_rr_per_rrset: bool
@ivar ignore_trailing: Ignore trailing junk at end of request?
@type ignore_trailing: bool
@ivar zone_rdclass: The class of the zone in messages which are
updating: Is the message a dynamic update?
one_rr_per_rrset: Put each RR into its own RRset?
ignore_trailing: Ignore trailing junk at end of request?
zone_rdclass: The class of the zone in messages which are
DNS dynamic updates.
@type zone_rdclass: int
"""
def __init__(self, wire, message, question_only=False,
@ -606,10 +608,9 @@ class _WireReader(object):
self.ignore_trailing = ignore_trailing
def _get_question(self, qcount):
"""Read the next I{qcount} records from the wire data and add them to
"""Read the next *qcount* records from the wire data and add them to
the question section.
@param qcount: the number of questions in the message
@type qcount: int"""
"""
if self.updating and qcount > 1:
raise dns.exception.FormError
@ -632,10 +633,10 @@ class _WireReader(object):
def _get_section(self, section, count):
"""Read the next I{count} records from the wire data and add them to
the specified section.
@param section: the section of the message to which to add records
@type section: list of dns.rrset.RRset objects
@param count: the number of records to read
@type count: int"""
section: the section of the message to which to add records
count: the number of records to read
"""
if self.updating or self.one_rr_per_rrset:
force_unique = True
@ -753,45 +754,58 @@ class _WireReader(object):
self.message.tsig_ctx.update(self.wire)
def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None,
def from_wire(wire, keyring=None, request_mac=b'', xfr=False, origin=None,
tsig_ctx=None, multi=False, first=True,
question_only=False, one_rr_per_rrset=False,
ignore_trailing=False):
"""Convert a DNS wire format message into a message
object.
@param keyring: The keyring to use if the message is signed.
@type keyring: dict
@param request_mac: If the message is a response to a TSIG-signed request,
I{request_mac} should be set to the MAC of that request.
@type request_mac: string
@param xfr: Is this message part of a zone transfer?
@type xfr: bool
@param origin: If the message is part of a zone transfer, I{origin}
should be the origin name of the zone.
@type origin: dns.name.Name object
@param tsig_ctx: The ongoing TSIG context, used when validating zone
transfers.
@type tsig_ctx: hmac.HMAC object
@param multi: Is this message part of a multiple message sequence?
@type multi: bool
@param first: Is this message standalone, or the first of a multi
message sequence?
@type first: bool
@param question_only: Read only up to the end of the question section?
@type question_only: bool
@param one_rr_per_rrset: Put each RR into its own RRset
@type one_rr_per_rrset: bool
@param ignore_trailing: Ignore trailing junk at end of request?
@type ignore_trailing: bool
@raises ShortHeader: The message is less than 12 octets long.
@raises TrailingJunk: There were octets in the message past the end
of the proper DNS message.
@raises BadEDNS: An OPT record was in the wrong section, or occurred more
than once.
@raises BadTSIG: A TSIG record was not the last record of the additional
data section.
@rtype: dns.message.Message object"""
*keyring*, a ``dict``, the keyring to use if the message is signed.
*request_mac*, a ``binary``. If the message is a response to a
TSIG-signed request, *request_mac* should be set to the MAC of
that request.
*xfr*, a ``bool``, should be set to ``True`` if this message is part of
a zone transfer.
*origin*, a ``dns.name.Name`` or ``None``. If the message is part
of a zone transfer, *origin* should be the origin name of the
zone.
*tsig_ctx*, a ``hmac.HMAC`` objext, the ongoing TSIG context, used
when validating zone transfers.
*multi*, a ``bool``, should be set to ``True`` if this message
part of a multiple message sequence.
*first*, a ``bool``, should be set to ``True`` if this message is
stand-alone, or the first message in a multi-message sequence.
*question_only*, a ``bool``. If ``True``, read only up to
the end of the question section.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its
own RRset.
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
junk at end of the message.
Raises ``dns.message.ShortHeader`` if the message is less than 12 octets
long.
Raises ``dns.messaage.TrailingJunk`` if there were octets in the message
past the end of the proper DNS message, and *ignore_trailing* is ``False``.
Raises ``dns.message.BadEDNS`` if an OPT record was in the
wrong section, or occurred more than once.
Raises ``dns.message.BadTSIG`` if a TSIG record was not the last
record of the additional data section.
Returns a ``dns.message.Message``.
"""
m = Message(id=0)
m.keyring = keyring
@ -813,18 +827,12 @@ class _TextReader(object):
"""Text format reader.
@ivar tok: the tokenizer
@type tok: dns.tokenizer.Tokenizer object
@ivar message: The message object being built
@type message: dns.message.Message object
@ivar updating: Is the message a dynamic update?
@type updating: bool
@ivar zone_rdclass: The class of the zone in messages which are
tok: the tokenizer.
message: The message object being built.
updating: Is the message a dynamic update?
zone_rdclass: The class of the zone in messages which are
DNS dynamic updates.
@type zone_rdclass: int
@ivar last_name: The most recently read name when building a message object
from text format.
@type last_name: dns.name.Name object
last_name: The most recently read name when building a message object.
"""
def __init__(self, text, message):
@ -997,11 +1005,14 @@ class _TextReader(object):
def from_text(text):
"""Convert the text format message into a message object.
@param text: The text format message.
@type text: string
@raises UnknownHeaderField:
@raises dns.exception.SyntaxError:
@rtype: dns.message.Message object"""
*text*, a ``text``, the text format message.
Raises ``dns.message.UnknownHeaderField`` if a header is unknown.
Raises ``dns.exception.SyntaxError`` if the text is badly formed.
Returns a ``dns.message.Message object``
"""
# 'text' can also be a file, but we don't publish that fact
# since it's an implementation detail. The official file
@ -1018,11 +1029,15 @@ def from_text(text):
def from_file(f):
"""Read the next text format message from the specified file.
@param f: file or string. If I{f} is a string, it is treated
as the name of a file to open.
@raises UnknownHeaderField:
@raises dns.exception.SyntaxError:
@rtype: dns.message.Message object"""
*f*, a ``file`` or ``text``. If *f* is text, it is treated as the
pathname of a file to open.
Raises ``dns.message.UnknownHeaderField`` if a header is unknown.
Raises ``dns.exception.SyntaxError`` if the text is badly formed.
Returns a ``dns.message.Message object``
"""
str_type = string_types
opts = 'rU'
@ -1052,30 +1067,35 @@ def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
The query will have a randomly chosen query id, and its DNS flags
will be set to dns.flags.RD.
@param qname: The query name.
@type qname: dns.name.Name object or string
@param rdtype: The desired rdata type.
@type rdtype: int
@param rdclass: The desired rdata class; the default is class IN.
@type rdclass: int
@param use_edns: The EDNS level to use; the default is None (no EDNS).
qname, a ``dns.name.Name`` or ``text``, the query name.
*rdtype*, an ``int`` or ``text``, the desired rdata type.
*rdclass*, an ``int`` or ``text``, the desired rdata class; the default
is class IN.
*use_edns*, an ``int``, ``bool`` or ``None``. The EDNS level to use; the
default is None (no EDNS).
See the description of dns.message.Message.use_edns() for the possible
values for use_edns and their meanings.
@type use_edns: int or bool or None
@param want_dnssec: Should the query indicate that DNSSEC is desired?
@type want_dnssec: bool
@param ednsflags: EDNS flag values.
@type ednsflags: int
@param payload: The EDNS sender's payload field, which is the maximum
size of UDP datagram the sender can handle.
@type payload: int
@param request_payload: The EDNS payload size to use when sending
this message. If not specified, defaults to the value of payload.
@type request_payload: int or None
@param options: The EDNS options
@type options: None or list of dns.edns.Option objects
@see: RFC 2671
@rtype: dns.message.Message object"""
*want_dnssec*, a ``bool``. If ``True``, DNSSEC data is desired.
*ednsflags*, an ``int``, the EDNS flag values.
*payload*, an ``int``, is the EDNS sender's payload field, which is the
maximum size of UDP datagram the sender can handle. I.e. how big
a response to this message can be.
*request_payload*, an ``int``, is the EDNS payload size to use when
sending this message. If not specified, defaults to the value of
*payload*.
*options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS
options.
Returns a ``dns.message.Message``
"""
if isinstance(qname, string_types):
qname = dns.name.from_text(qname)
@ -1124,16 +1144,17 @@ def make_response(query, recursion_available=False, our_payload=8192,
question section, so the query's question RRsets should not be
changed.
@param query: the query to respond to
@type query: dns.message.Message object
@param recursion_available: should RA be set in the response?
@type recursion_available: bool
@param our_payload: payload size to advertise in EDNS responses; default
is 8192.
@type our_payload: int
@param fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@rtype: dns.message.Message object"""
*query*, a ``dns.message.Message``, the query to respond to.
*recursion_available*, a ``bool``, should RA be set in the response?
*our_payload*, an ``int``, the payload size to advertise in EDNS
responses.
*fudge*, an ``int``, the TSIG time fudge.
Returns a ``dns.message.Message`` object.
"""
if query.flags & dns.flags.QR:
raise dns.exception.FormError('specified query message is not a query')
@ -1146,7 +1167,7 @@ def make_response(query, recursion_available=False, our_payload=8192,
if query.edns >= 0:
response.use_edns(0, 0, our_payload, query.payload)
if query.had_tsig:
response.use_tsig(query.keyring, query.keyname, fudge, None, 0, '',
response.use_tsig(query.keyring, query.keyname, fudge, None, 0, b'',
query.keyalgorithm)
response.request_mac = query.mac
return response

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -14,11 +14,6 @@
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Names.
@var root: The DNS root name.
@type root: dns.name.Name object
@var empty: The empty DNS name.
@type empty: dns.name.Name object
"""
from io import BytesIO
@ -35,95 +30,117 @@ except ImportError:
import dns.exception
import dns.wiredata
from ._compat import long, binary_type, text_type, unichr
from ._compat import long, binary_type, text_type, unichr, maybe_decode
try:
maxint = sys.maxint
except AttributeError:
maxint = (1 << (8 * struct.calcsize("P"))) // 2 - 1
# fullcompare() result values
#: The compared names have no relationship to each other.
NAMERELN_NONE = 0
#: the first name is a superdomain of the second.
NAMERELN_SUPERDOMAIN = 1
#: The first name is a subdomain of the second.
NAMERELN_SUBDOMAIN = 2
#: The compared names are equal.
NAMERELN_EQUAL = 3
#: The compared names have a common ancestor.
NAMERELN_COMMONANCESTOR = 4
class EmptyLabel(dns.exception.SyntaxError):
"""A DNS label is empty."""
class BadEscape(dns.exception.SyntaxError):
"""An escaped code in a text format of DNS name is invalid."""
class BadPointer(dns.exception.FormError):
"""A DNS compression pointer points forward instead of backward."""
class BadLabelType(dns.exception.FormError):
"""The label type in DNS name wire format is unknown."""
class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
"""An attempt was made to convert a non-absolute name to
wire when there was also a non-absolute (or missing) origin."""
class NameTooLong(dns.exception.FormError):
"""A DNS name is > 255 octets long."""
class LabelTooLong(dns.exception.SyntaxError):
"""A DNS label is > 63 octets long."""
class AbsoluteConcatenation(dns.exception.DNSException):
"""An attempt was made to append anything other than the
empty name to an absolute DNS name."""
class NoParent(dns.exception.DNSException):
"""An attempt was made to get the parent of the root 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."""
"""IDNA processing raised an exception."""
supp_kwargs = set(['idna_exception'])
fmt = "IDNA processing exception: {idna_exception}"
class IDNACodec(object):
class IDNACodec(object):
"""Abstract base class for IDNA encoder/decoders."""
def __init__(self):
pass
def encode(self, label):
raise NotImplementedError
def decode(self, label):
raise NotImplementedError
# We do not apply any IDNA policy on decode; we just
downcased = label.lower()
if downcased.startswith(b'xn--'):
try:
label = downcased[4:].decode('punycode')
except Exception as e:
raise IDNAException(idna_exception=e)
else:
label = maybe_decode(label)
return _escapify(label, True)
class IDNA2003Codec(IDNACodec):
"""IDNA 2003 encoder/decoder."""
def __init__(self, strict_decode=False):
"""Initialize the IDNA 2003 encoder/decoder.
*strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
is done when decoding. This can cause failures if the name
was encoded with IDNA2008. The default is `False`.
"""
super(IDNA2003Codec, self).__init__()
self.strict_decode = strict_decode
def encode(self, label):
"""Encode *label*."""
if label == '':
return b''
try:
@ -132,37 +149,49 @@ class IDNA2003Codec(IDNACodec):
raise LabelTooLong
def decode(self, label):
"""Decode *label*."""
if not self.strict_decode:
return super(IDNA2003Codec, self).decode(label)
if label == b'':
return u''
return _escapify(encodings.idna.ToUnicode(label), True)
try:
return _escapify(encodings.idna.ToUnicode(label), True)
except Exception as e:
raise IDNAException(idna_exception=e)
class IDNA2008Codec(IDNACodec):
"""IDNA 2008 encoder/decoder.
"""IDNA 2008 encoder/decoder."""
*uts_46* is a ``bool``. If True, apply Unicode IDNA
compatibility processing as described in Unicode Technical
Standard #46 (http://unicode.org/reports/tr46/).
If False, do not apply the mapping. The default is False.
*transitional* is a ``bool``: If True, use the
"transitional" mode described in Unicode Technical Standard
#46. The default is False.
*allow_pure_ascii* is a ``bool``. 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 suffix which would otherwise be disallowed. The
default is False.
*strict_decode* is a ``bool``: If True, then IDNA2008 checking
is done when decoding. This can cause failures if the name
was encoded with IDNA2003. The default is False.
"""
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
"""
allow_pure_ascii=False, strict_decode=False):
"""Initialize the IDNA 2008 encoder/decoder."""
super(IDNA2008Codec, self).__init__()
self.uts_46 = uts_46
self.transitional = transitional
self.allow_pure_ascii = allow_pure_ascii
self.strict_decode = strict_decode
def is_all_ascii(self, label):
for c in label:
@ -185,6 +214,8 @@ class IDNA2008Codec(IDNACodec):
raise IDNAException(idna_exception=e)
def decode(self, label):
if not self.strict_decode:
return super(IDNA2008Codec, self).decode(label)
if label == b'':
return u''
if not have_idna_2008:
@ -196,14 +227,15 @@ class IDNA2008Codec(IDNACodec):
except idna.IDNAError as e:
raise IDNAException(idna_exception=e)
_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_2003_Practical = IDNA2003Codec(False)
IDNA_2003_Strict = IDNA2003Codec(True)
IDNA_2003 = IDNA_2003_Practical
IDNA_2008_Practical = IDNA2008Codec(True, False, True, False)
IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False)
IDNA_2008_Strict = IDNA2008Codec(False, False, False, True)
IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False)
IDNA_2008 = IDNA_2008_Practical
def _escapify(label, unicode_mode=False):
@ -241,9 +273,14 @@ def _escapify(label, unicode_mode=False):
def _validate_labels(labels):
"""Check for empty labels in the middle of a label sequence,
labels that are too long, and for too many labels.
@raises NameTooLong: the name as a whole is too long
@raises EmptyLabel: a label is empty (i.e. the root label) and appears
in a position other than the end of the label sequence"""
Raises ``dns.name.NameTooLong`` if the name as a whole is too long.
Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
label) and appears in a position other than the end of the label
sequence
"""
l = len(labels)
total = 0
@ -263,7 +300,12 @@ def _validate_labels(labels):
raise EmptyLabel
def _ensure_bytes(label):
def _maybe_convert_to_binary(label):
"""If label is ``text``, convert it to ``binary``. If it is already
``binary`` just return it.
"""
if isinstance(label, binary_type):
return label
if isinstance(label, text_type):
@ -275,24 +317,23 @@ class Name(object):
"""A DNS name.
The dns.name.Name class represents a DNS name as a tuple of labels.
Instances of the class are immutable.
@ivar labels: The tuple of labels in the name. Each label is a string of
up to 63 octets."""
The dns.name.Name class represents a DNS name as a tuple of
labels. Each label is a `binary` in DNS wire format. Instances
of the class are immutable.
"""
__slots__ = ['labels']
def __init__(self, labels):
"""Initialize a domain name from a list of labels.
@param labels: the labels
@type labels: any iterable whose values are strings
"""*labels* is any iterable whose values are ``text`` or ``binary``.
"""
labels = [_ensure_bytes(x) for x in labels]
labels = [_maybe_convert_to_binary(x) for x in labels]
super(Name, self).__setattr__('labels', tuple(labels))
_validate_labels(self.labels)
def __setattr__(self, name, value):
# Names are immutable
raise TypeError("object doesn't support attribute assignment")
def __copy__(self):
@ -302,6 +343,7 @@ class Name(object):
return Name(copy.deepcopy(self.labels, memo))
def __getstate__(self):
# Names can be pickled
return {'labels': self.labels}
def __setstate__(self, state):
@ -310,21 +352,24 @@ class Name(object):
def is_absolute(self):
"""Is the most significant label of this name the root label?
@rtype: bool
Returns a ``bool``.
"""
return len(self.labels) > 0 and self.labels[-1] == b''
def is_wild(self):
"""Is this name wild? (I.e. Is the least significant label '*'?)
@rtype: bool
Returns a ``bool``.
"""
return len(self.labels) > 0 and self.labels[0] == b'*'
def __hash__(self):
"""Return a case-insensitive hash of the name.
@rtype: int
Returns an ``int``.
"""
h = long(0)
@ -334,20 +379,35 @@ class Name(object):
return int(h % maxint)
def fullcompare(self, other):
"""Compare two names, returning a 3-tuple (relation, order, nlabels).
"""Compare two names, returning a 3-tuple
``(relation, order, nlabels)``.
I{relation} describes the relation ship between the names,
and is one of: dns.name.NAMERELN_NONE,
dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN,
dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR
*relation* describes the relation ship between the names,
and is one of: ``dns.name.NAMERELN_NONE``,
``dns.name.NAMERELN_SUPERDOMAIN``, ``dns.name.NAMERELN_SUBDOMAIN``,
``dns.name.NAMERELN_EQUAL``, or ``dns.name.NAMERELN_COMMONANCESTOR``.
I{order} is < 0 if self < other, > 0 if self > other, and ==
0 if self == other. A relative name is always less than an
*order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
0 if *self* == *other*. A relative name is always less than an
absolute name. If both names have the same relativity, then
the DNSSEC order relation is used to order them.
I{nlabels} is the number of significant labels that the two names
*nlabels* is the number of significant labels that the two names
have in common.
Here are some examples. Names ending in "." are absolute names,
those not ending in "." are relative names.
============= ============= =========== ===== =======
self other relation order nlabels
============= ============= =========== ===== =======
www.example. www.example. equal 0 3
www.example. example. subdomain > 0 2
example. www.example. superdomain < 0 2
example1.com. example2.com. common anc. < 0 2
example1 example2. none < 0 0
example1. example2 none > 0 0
============= ============= =========== ===== =======
"""
sabs = self.is_absolute()
@ -397,8 +457,10 @@ class Name(object):
def is_subdomain(self, other):
"""Is self a subdomain of other?
The notion of subdomain includes equality.
@rtype: bool
Note that the notion of subdomain includes equality, e.g.
"dnpython.org" is a subdomain of itself.
Returns a ``bool``.
"""
(nr, o, nl) = self.fullcompare(other)
@ -409,8 +471,10 @@ class Name(object):
def is_superdomain(self, other):
"""Is self a superdomain of other?
The notion of subdomain includes equality.
@rtype: bool
Note that the notion of superdomain includes equality, e.g.
"dnpython.org" is a superdomain of itself.
Returns a ``bool``.
"""
(nr, o, nl) = self.fullcompare(other)
@ -421,7 +485,6 @@ class Name(object):
def canonicalize(self):
"""Return a name which is equal to the current name, but is in
DNSSEC canonical form.
@rtype: dns.name.Name object
"""
return Name([x.lower() for x in self.labels])
@ -466,39 +529,45 @@ class Name(object):
return '<DNS name ' + self.__str__() + '>'
def __str__(self):
return self.to_text(False).decode()
return self.to_text(False)
def to_text(self, omit_final_dot=False):
"""Convert name to text format.
@param omit_final_dot: If True, don't emit the final dot (denoting the
root label) for absolute names. The default is False.
@rtype: string
"""Convert name to DNS text format.
*omit_final_dot* is a ``bool``. If True, don't emit the final
dot (denoting the root label) for absolute names. The default
is False.
Returns a ``text``.
"""
if len(self.labels) == 0:
return b'@'
return maybe_decode(b'@')
if len(self.labels) == 1 and self.labels[0] == b'':
return b'.'
return maybe_decode(b'.')
if omit_final_dot and self.is_absolute():
l = self.labels[:-1]
else:
l = self.labels
s = b'.'.join(map(_escapify, l))
return s
return maybe_decode(s)
def to_unicode(self, omit_final_dot=False, idna_codec=None):
"""Convert name to Unicode text format.
IDN ACE labels are converted to Unicode.
@param omit_final_dot: If True, don't emit the final dot (denoting the
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
*omit_final_dot* is a ``bool``. If True, don't emit the final
dot (denoting the root label) for absolute names. The default
is False.
*idna_codec* specifies the IDNA encoder/decoder. If None, the
dns.name.IDNA_2003_Practical encoder/decoder is used.
The IDNA_2003_Practical decoder does
not impose any policy, it just decodes punycode, so if you
don't want checking for compliance, you can use this decoder
for IDNA2008 as well.
Returns a ``text``.
"""
if len(self.labels) == 0:
@ -510,21 +579,24 @@ class Name(object):
else:
l = self.labels
if idna_codec is None:
idna_codec = IDNA_2003
idna_codec = IDNA_2003_Practical
return u'.'.join([idna_codec.decode(x) for x in l])
def to_digestable(self, origin=None):
"""Convert name to a format suitable for digesting in hashes.
The name is canonicalized and converted to uncompressed wire format.
The name is canonicalized and converted to uncompressed wire
format. All names in wire format are absolute. If the name
is a relative name, then an origin must be supplied.
@param origin: If the name is relative and origin is not None, then
origin will be appended to it.
@type origin: dns.name.Name object
@raises NeedAbsoluteNameOrOrigin: All names in wire format are
absolute. If self is a relative name, then an origin must be supplied;
if it is missing, then this exception is raised
@rtype: string
*origin* is a ``dns.name.Name`` or ``None``. If the name is
relative and origin is not ``None``, then origin will be appended
to the name.
Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
relative and no origin was provided.
Returns a ``binary``.
"""
if not self.is_absolute():
@ -541,19 +613,21 @@ class Name(object):
def to_wire(self, file=None, compress=None, origin=None):
"""Convert name to wire format, possibly compressing it.
@param file: the file where the name is emitted (typically
a BytesIO file). If None, a string containing the wire name
will be returned.
@type file: file or None
@param compress: The compression table. If None (the default) names
will not be compressed.
@type compress: dict
@param origin: If the name is relative and origin is not None, then
origin will be appended to it.
@type origin: dns.name.Name object
@raises NeedAbsoluteNameOrOrigin: All names in wire format are
absolute. If self is a relative name, then an origin must be supplied;
if it is missing, then this exception is raised
*file* is the file where the name is emitted (typically a
BytesIO file). If ``None`` (the default), a ``binary``
containing the wire name will be returned.
*compress*, a ``dict``, is the compression table to use. If
``None`` (the default), names will not be compressed.
*origin* is a ``dns.name.Name`` or ``None``. If the name is
relative and origin is not ``None``, then *origin* will be appended
to it.
Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
relative and no origin was provided.
Returns a ``binary`` or ``None``.
"""
if file is None:
@ -596,7 +670,8 @@ class Name(object):
def __len__(self):
"""The length of the name (in labels).
@rtype: int
Returns an ``int``.
"""
return len(self.labels)
@ -611,14 +686,14 @@ class Name(object):
return self.relativize(other)
def split(self, depth):
"""Split a name into a prefix and suffix at depth.
"""Split a name into a prefix and suffix names at the specified depth.
@param depth: the number of labels in the suffix
@type depth: int
@raises ValueError: the depth was not >= 0 and <= the length of the
*depth* is an ``int`` specifying the number of labels in the suffix
Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
name.
@returns: the tuple (prefix, suffix)
@rtype: tuple
Returns the tuple ``(prefix, suffix)``.
"""
l = len(self.labels)
@ -633,9 +708,11 @@ class Name(object):
def concatenate(self, other):
"""Return a new name which is the concatenation of self and other.
@rtype: dns.name.Name object
@raises AbsoluteConcatenation: self is absolute and other is
not the empty name
Raises ``dns.name.AbsoluteConcatenation`` if the name is
absolute and *other* is not the empty name.
Returns a ``dns.name.Name``.
"""
if self.is_absolute() and len(other) > 0:
@ -645,9 +722,14 @@ class Name(object):
return Name(labels)
def relativize(self, origin):
"""If self is a subdomain of origin, return a new name which is self
relative to origin. Otherwise return self.
@rtype: dns.name.Name object
"""If the name is a subdomain of *origin*, return a new name which is
the name relative to origin. Otherwise return the name.
For example, relativizing ``www.dnspython.org.`` to origin
``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
to origin ``dnspython.org.`` returns ``example.``.
Returns a ``dns.name.Name``.
"""
if origin is not None and self.is_subdomain(origin):
@ -656,9 +738,14 @@ class Name(object):
return self
def derelativize(self, origin):
"""If self is a relative name, return a new name which is the
concatenation of self and origin. Otherwise return self.
@rtype: dns.name.Name object
"""If the name is a relative name, return a new name which is the
concatenation of the name and origin. Otherwise return the name.
For example, derelativizing ``www`` to origin ``dnspython.org.``
returns the name ``www.dnspython.org.``. Derelativizing ``example.``
to origin ``dnspython.org.`` returns ``example.``.
Returns a ``dns.name.Name``.
"""
if not self.is_absolute():
@ -667,11 +754,14 @@ class Name(object):
return self
def choose_relativity(self, origin=None, relativize=True):
"""Return a name with the relativity desired by the caller. If
origin is None, then self is returned. Otherwise, if
relativize is true the name is relativized, and if relativize is
false the name is derelativized.
@rtype: dns.name.Name object
"""Return a name with the relativity desired by the caller.
If *origin* is ``None``, then the name is returned.
Otherwise, if *relativize* is ``True`` the name is
relativized, and if *relativize* is ``False`` the name is
derelativized.
Returns a ``dns.name.Name``.
"""
if origin:
@ -684,31 +774,41 @@ class Name(object):
def parent(self):
"""Return the parent of the name.
@rtype: dns.name.Name object
@raises NoParent: the name is either the root name or the empty name,
and thus has no parent.
For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
Raises ``dns.name.NoParent`` if the name is either the root name or the
empty name, and thus has no parent.
Returns a ``dns.name.Name``.
"""
if self == root or self == empty:
raise NoParent
return Name(self.labels[1:])
#: The root name, '.'
root = Name([b''])
empty = Name([])
#: The empty name.
empty = Name([])
def from_unicode(text, origin=root, idna_codec=None):
"""Convert unicode text into a Name object.
Labels are encoded in IDN ACE form.
Labels are encoded in IDN ACE form according to rules specified by
the IDNA codec.
@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
*text*, a ``text``, is the text to convert into a name.
*origin*, a ``dns.name.Name``, specifies the origin to
append to non-absolute names. The default is the root name.
*idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
is used.
Returns a ``dns.name.Name``.
"""
if not isinstance(text, text_type):
@ -771,14 +871,16 @@ def from_unicode(text, origin=root, idna_codec=None):
def from_text(text, origin=root, idna_codec=None):
"""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
*text*, a ``text``, is the text to convert into a name.
*origin*, a ``dns.name.Name``, specifies the origin to
append to non-absolute names. The default is the root name.
*idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
is used.
Returns a ``dns.name.Name``.
"""
if isinstance(text, text_type):
@ -840,17 +942,21 @@ def from_text(text, origin=root, idna_codec=None):
def from_wire(message, current):
"""Convert possibly compressed wire format into a Name.
@param message: the entire DNS message
@type message: string
@param current: the offset of the beginning of the name from the start
of the message
@type current: int
@raises dns.name.BadPointer: a compression pointer did not point backwards
in the message
@raises dns.name.BadLabelType: an invalid label type was encountered.
@returns: a tuple consisting of the name that was read and the number
of bytes of the wire format message which were consumed reading it
@rtype: (dns.name.Name object, int) tuple
*message* is a ``binary`` containing an entire DNS message in DNS
wire form.
*current*, an ``int``, is the offset of the beginning of the name
from the start of the message
Raises ``dns.name.BadPointer`` if a compression pointer did not
point backwards in the message.
Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
Returns a ``(dns.name.Name, int)`` tuple consisting of the name
that was read and the number of bytes of the wire format message
which were consumed reading it.
"""
if not isinstance(message, binary_type):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
# Copyright (C) 2016 Coresec Systems AB
#
# Permission to use, copy, modify, and distribute this software and its
@ -31,20 +31,19 @@ from ._compat import xrange
class NameDict(collections.MutableMapping):
"""A dictionary whose keys are dns.name.Name objects.
@ivar max_depth: the maximum depth of the keys that have ever been
added to the dictionary.
@type max_depth: int
@ivar max_depth_items: the number of items of maximum depth
@type max_depth_items: int
In addition to being like a regular Python dictionary, this
dictionary can also get the deepest match for a given key.
"""
__slots__ = ["max_depth", "max_depth_items", "__store"]
def __init__(self, *args, **kwargs):
self.__store = dict()
#: the maximum depth of the keys that have ever been added
self.max_depth = 0
#: the number of items of maximum depth
self.max_depth_items = 0
self.update(dict(*args, **kwargs))
@ -83,14 +82,16 @@ class NameDict(collections.MutableMapping):
return key in self.__store
def get_deepest_match(self, name):
"""Find the deepest match to I{name} in the dictionary.
"""Find the deepest match to *fname* in the dictionary.
The deepest match is the longest name in the dictionary which is
a superdomain of I{name}.
a superdomain of *name*. Note that *superdomain* includes matching
*name* itself.
@param name: the name
@type name: dns.name.Name object
@rtype: (key, value) tuple
*name*, a ``dns.name.Name``, the name to find.
Returns a ``(key, value)`` where *key* is the deepest
``dns.name.Name``, and *value* is the value associated with *key*.
"""
depth = len(name)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -24,19 +24,12 @@ import dns.renderer
class Node(object):
"""A DNS node.
A node is a set of rdatasets
@ivar rdatasets: the node's rdatasets
@type rdatasets: list of dns.rdataset.Rdataset objects"""
"""A Node is a set of rdatasets."""
__slots__ = ['rdatasets']
def __init__(self):
"""Initialize a DNS node.
"""
#: the set of rdatsets, represented as a list.
self.rdatasets = []
def to_text(self, name, **kw):
@ -44,9 +37,10 @@ class Node(object):
Each rdataset at the node is printed. Any keyword arguments
to this method are passed on to the rdataset's to_text() method.
@param name: the owner name of the rdatasets
@type name: dns.name.Name object
@rtype: string
*name*, a ``dns.name.Name``, the owner name of the rdatasets.
Returns a ``text``.
"""
s = StringIO()
@ -60,10 +54,6 @@ class Node(object):
return '<DNS node ' + str(id(self)) + '>'
def __eq__(self, other):
"""Two nodes are equal if they have the same rdatasets.
@rtype: bool
"""
#
# This is inefficient. Good thing we don't need to do it much.
#
@ -89,11 +79,11 @@ class Node(object):
"""Find an rdataset matching the specified properties in the
current node.
@param rdclass: The class of the rdataset
@type rdclass: int
@param rdtype: The type of the rdataset
@type rdtype: int
@param covers: The covered type. Usually this value is
*rdclass*, an ``int``, the class of the rdataset.
*rdtype*, an ``int``, the type of the rdataset.
*covers*, an ``int``, the covered type. Usually this value is
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
@ -101,12 +91,13 @@ class Node(object):
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
@type covers: int
@param create: If True, create the rdataset if it is not found.
@type create: bool
@raises KeyError: An rdataset of the desired type and class does
not exist and I{create} is not True.
@rtype: dns.rdataset.Rdataset object
*create*, a ``bool``. If True, create the rdataset if it is not found.
Raises ``KeyError`` if an rdataset of the desired type and class does
not exist and *create* is not ``True``.
Returns a ``dns.rdataset.Rdataset``.
"""
for rds in self.rdatasets:
@ -124,17 +115,24 @@ class Node(object):
current node.
None is returned if an rdataset of the specified type and
class does not exist and I{create} is not True.
class does not exist and *create* is not ``True``.
@param rdclass: The class of the rdataset
@type rdclass: int
@param rdtype: The type of the rdataset
@type rdtype: int
@param covers: The covered type.
@type covers: int
@param create: If True, create the rdataset if it is not found.
@type create: bool
@rtype: dns.rdataset.Rdataset object or None
*rdclass*, an ``int``, the class of the rdataset.
*rdtype*, an ``int``, the type of the rdataset.
*covers*, an ``int``, the covered type. Usually this value is
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
types as if they were a family of
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
*create*, a ``bool``. If True, create the rdataset if it is not found.
Returns a ``dns.rdataset.Rdataset`` or ``None``.
"""
try:
@ -149,12 +147,11 @@ class Node(object):
If a matching rdataset does not exist, it is not an error.
@param rdclass: The class of the rdataset
@type rdclass: int
@param rdtype: The type of the rdataset
@type rdtype: int
@param covers: The covered type.
@type covers: int
*rdclass*, an ``int``, the class of the rdataset.
*rdtype*, an ``int``, the type of the rdataset.
*covers*, an ``int``, the covered type.
"""
rds = self.get_rdataset(rdclass, rdtype, covers)
@ -164,11 +161,16 @@ class Node(object):
def replace_rdataset(self, replacement):
"""Replace an rdataset.
It is not an error if there is no rdataset matching I{replacement}.
It is not an error if there is no rdataset matching *replacement*.
Ownership of the I{replacement} object is transferred to the node;
in other words, this method does not store a copy of I{replacement}
at the node, it stores I{replacement} itself.
Ownership of the *replacement* object is transferred to the node;
in other words, this method does not store a copy of *replacement*
at the node, it stores *replacement* itself.
*replacement*, a ``dns.rdataset.Rdataset``.
Raises ``ValueError`` if *replacement* is not a
``dns.rdataset.Rdataset``.
"""
if not isinstance(replacement, dns.rdataset.Rdataset):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -17,10 +17,15 @@
import dns.exception
#: Query
QUERY = 0
#: Inverse Query (historical)
IQUERY = 1
#: Server Status (unspecified and unimplemented anywhere)
STATUS = 2
#: Notify
NOTIFY = 4
#: Dynamic Update
UPDATE = 5
_by_text = {
@ -39,17 +44,17 @@ _by_value = dict((y, x) for x, y in _by_text.items())
class UnknownOpcode(dns.exception.DNSException):
"""An DNS opcode is unknown."""
def from_text(text):
"""Convert text into an opcode.
@param text: the textual opcode
@type text: string
@raises UnknownOpcode: the opcode is unknown
@rtype: int
*text*, a ``text``, the textual opcode
Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown.
Returns an ``int``.
"""
if text.isdigit():
@ -65,8 +70,9 @@ def from_text(text):
def from_flags(flags):
"""Extract an opcode from DNS message flags.
@param flags: int
@rtype: int
*flags*, an ``int``, the DNS flags.
Returns an ``int``.
"""
return (flags & 0x7800) >> 11
@ -75,7 +81,10 @@ def from_flags(flags):
def to_flags(value):
"""Convert an opcode to a value suitable for ORing into DNS message
flags.
@rtype: int
*value*, an ``int``, the DNS opcode value.
Returns an ``int``.
"""
return (value << 11) & 0x7800
@ -84,10 +93,11 @@ def to_flags(value):
def to_text(value):
"""Convert an opcode to text.
@param value: the opcdoe
@type value: int
@raises UnknownOpcode: the opcode is unknown
@rtype: string
*value*, an ``int`` the opcode value,
Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown.
Returns a ``text``.
"""
text = _by_value.get(value)
@ -97,11 +107,11 @@ def to_text(value):
def is_update(flags):
"""True if the opcode in flags is UPDATE.
"""Is the opcode in flags UPDATE?
@param flags: DNS flags
@type flags: int
@rtype: bool
*flags*, an ``int``, the DNS message flags.
Returns a ``bool``.
"""
return from_flags(flags) == UPDATE

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -28,6 +28,7 @@ import dns.exception
import dns.inet
import dns.name
import dns.message
import dns.rcode
import dns.rdataclass
import dns.rdatatype
from ._compat import long, string_types
@ -42,34 +43,36 @@ else:
socket_factory = socket.socket
class UnexpectedSource(dns.exception.DNSException):
"""A DNS query response came from an unexpected address or port."""
class BadResponse(dns.exception.FormError):
"""A DNS query response does not respond to the question asked."""
class TransferError(dns.exception.DNSException):
"""A zone transfer response got a non-zero rcode."""
def __init__(self, rcode):
message = 'Zone transfer error: %s' % dns.rcode.to_text(rcode)
super(TransferError, self).__init__(message)
self.rcode = rcode
def _compute_expiration(timeout):
if timeout is None:
return None
else:
return time.time() + timeout
# This module can use either poll() or select() as the "polling backend".
#
# A backend function takes an fd, bools for readability, writablity, and
# error detection, and a timeout.
def _poll_for(fd, readable, writable, error, timeout):
"""Poll polling backend.
@param fd: File descriptor
@type fd: int
@param readable: Whether to wait for readability
@type readable: bool
@param writable: Whether to wait for writability
@type writable: bool
@param timeout: Deadline timeout (expiration time, in seconds)
@type timeout: float
@return True on success, False on timeout
"""
"""Poll polling backend."""
event_mask = 0
if readable:
event_mask |= select.POLLIN
@ -90,17 +93,8 @@ def _poll_for(fd, readable, writable, error, timeout):
def _select_for(fd, readable, writable, error, timeout):
"""Select polling backend.
@param fd: File descriptor
@type fd: int
@param readable: Whether to wait for readability
@type readable: bool
@param writable: Whether to wait for writability
@type writable: bool
@param timeout: Deadline timeout (expiration time, in seconds)
@type timeout: float
@return True on success, False on timeout
"""
"""Select polling backend."""
rset, wset, xset = [], [], []
if readable:
@ -119,6 +113,10 @@ def _select_for(fd, readable, writable, error, timeout):
def _wait_for(fd, readable, writable, error, expiration):
# Use the selected polling backend to wait for any of the specified
# events. An "expiration" absolute time is converted into a relative
# timeout.
done = False
while not done:
if expiration is None:
@ -137,9 +135,8 @@ def _wait_for(fd, readable, writable, error, expiration):
def _set_polling_backend(fn):
"""
Internal API. Do not use.
"""
# Internal API. Do not use.
global _polling_backend
_polling_backend = fn
@ -165,8 +162,11 @@ def _addresses_equal(af, a1, a2):
# Convert the first value of the tuple, which is a textual format
# address into binary form, so that we are not confused by different
# textual representations of the same address
n1 = dns.inet.inet_pton(af, a1[0])
n2 = dns.inet.inet_pton(af, a2[0])
try:
n1 = dns.inet.inet_pton(af, a1[0])
n2 = dns.inet.inet_pton(af, a2[0])
except dns.exception.SyntaxError:
return False
return n1 == n2 and a1[1:] == a2[1:]
@ -193,68 +193,133 @@ def _destination_and_source(af, where, port, source, source_port):
return (af, destination, source)
def send_udp(sock, what, destination, expiration=None):
"""Send a DNS message to the specified UDP socket.
*sock*, a ``socket``.
*what*, a ``binary`` or ``dns.message.Message``, the message to send.
*destination*, a destination tuple appropriate for the address family
of the socket, specifying where to send the query.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
Returns an ``(int, float)`` tuple of bytes sent and the sent time.
"""
if isinstance(what, dns.message.Message):
what = what.to_wire()
_wait_for_writable(sock, expiration)
sent_time = time.time()
n = sock.sendto(what, destination)
return (n, sent_time)
def receive_udp(sock, destination, expiration=None,
ignore_unexpected=False, one_rr_per_rrset=False,
keyring=None, request_mac=b''):
"""Read a DNS message from a UDP socket.
*sock*, a ``socket``.
*destination*, a destination tuple appropriate for the address family
of the socket, specifying where the associated query was sent.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
unexpected sources.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
*keyring*, a ``dict``, the keyring to use for TSIG.
*request_mac*, a ``binary``, the MAC of the request (for TSIG).
Raises if the message is malformed, if network errors occur, of if
there is a timeout.
Returns a ``dns.message.Message`` object.
"""
wire = b''
while 1:
_wait_for_readable(sock, expiration)
(wire, from_address) = sock.recvfrom(65535)
if _addresses_equal(sock.family, from_address, destination) or \
(dns.inet.is_multicast(destination[0]) and
from_address[1:] == destination[1:]):
break
if not ignore_unexpected:
raise UnexpectedSource('got a response from '
'%s instead of %s' % (from_address,
destination))
received_time = time.time()
r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
one_rr_per_rrset=one_rr_per_rrset)
return (r, received_time)
def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
ignore_unexpected=False, one_rr_per_rrset=False):
"""Return the response obtained after sending a query via UDP.
@param q: the query
@type q: dns.message.Message
@param where: where to send the message
@type where: string containing an IPv4 or IPv6 address
@param timeout: The number of seconds to wait before the query times out.
If None, the default, wait forever.
@type timeout: float
@param port: The port to which to send the message. The default is 53.
@type port: int
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of where.
If the inference attempt fails, AF_INET is used.
@type af: int
@rtype: dns.message.Message object
@param source: source address. The default is the wildcard address.
@type source: string
@param source_port: The port from which to send the message.
*q*, a ``dns.message.message``, the query to send
*where*, a ``text`` containing an IPv4 or IPv6 address, where
to send the message.
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
query times out. If ``None``, the default, wait forever.
*port*, an ``int``, the port send the message to. The default is 53.
*af*, an ``int``, the address family to use. The default is ``None``,
which causes the address family to use to be inferred from the form of
*where*. If the inference attempt fails, AF_INET is used. This
parameter is historical; you need never set it.
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
the source address. The default is the wildcard address.
*source_port*, an ``int``, the port from which to send the message.
The default is 0.
@type source_port: int
@param ignore_unexpected: If True, ignore responses from unexpected
sources. The default is False.
@type ignore_unexpected: bool
@param one_rr_per_rrset: Put each RR into its own RRset
@type one_rr_per_rrset: bool
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
unexpected sources.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
Returns a ``dns.message.Message``.
"""
wire = q.to_wire()
(af, destination, source) = _destination_and_source(af, where, port,
source, source_port)
s = socket_factory(af, socket.SOCK_DGRAM, 0)
begin_time = None
received_time = None
sent_time = None
try:
expiration = _compute_expiration(timeout)
s.setblocking(0)
if source is not None:
s.bind(source)
_wait_for_writable(s, expiration)
begin_time = time.time()
s.sendto(wire, destination)
while 1:
_wait_for_readable(s, expiration)
(wire, from_address) = s.recvfrom(65535)
if _addresses_equal(af, from_address, destination) or \
(dns.inet.is_multicast(where) and
from_address[1:] == destination[1:]):
break
if not ignore_unexpected:
raise UnexpectedSource('got a response from '
'%s instead of %s' % (from_address,
destination))
(_, sent_time) = send_udp(s, wire, destination, expiration)
(r, received_time) = receive_udp(s, destination, expiration,
ignore_unexpected, one_rr_per_rrset,
q.keyring, q.mac)
finally:
if begin_time is None:
if sent_time is None or received_time is None:
response_time = 0
else:
response_time = time.time() - begin_time
response_time = received_time - sent_time
s.close()
r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
one_rr_per_rrset=one_rr_per_rrset)
r.time = response_time
if not q.is_response(r):
raise BadResponse
@ -290,6 +355,63 @@ def _net_write(sock, data, expiration):
current += sock.send(data[current:])
def send_tcp(sock, what, expiration=None):
"""Send a DNS message to the specified TCP socket.
*sock*, a ``socket``.
*what*, a ``binary`` or ``dns.message.Message``, the message to send.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
Returns an ``(int, float)`` tuple of bytes sent and the sent time.
"""
if isinstance(what, dns.message.Message):
what = what.to_wire()
l = len(what)
# copying the wire into tcpmsg is inefficient, but lets us
# avoid writev() or doing a short write that would get pushed
# onto the net
tcpmsg = struct.pack("!H", l) + what
_wait_for_writable(sock, expiration)
sent_time = time.time()
_net_write(sock, tcpmsg, expiration)
return (len(tcpmsg), sent_time)
def receive_tcp(sock, expiration=None, one_rr_per_rrset=False,
keyring=None, request_mac=b''):
"""Read a DNS message from a TCP socket.
*sock*, a ``socket``.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
*keyring*, a ``dict``, the keyring to use for TSIG.
*request_mac*, a ``binary``, the MAC of the request (for TSIG).
Raises if the message is malformed, if network errors occur, of if
there is a timeout.
Returns a ``dns.message.Message`` object.
"""
ldata = _net_read(sock, 2, expiration)
(l,) = struct.unpack("!H", ldata)
wire = _net_read(sock, l, expiration)
received_time = time.time()
r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
one_rr_per_rrset=one_rr_per_rrset)
return (r, received_time)
def _connect(s, address):
try:
s.connect(address)
@ -308,27 +430,31 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
one_rr_per_rrset=False):
"""Return the response obtained after sending a query via TCP.
@param q: the query
@type q: dns.message.Message object
@param where: where to send the message
@type where: string containing an IPv4 or IPv6 address
@param timeout: The number of seconds to wait before the query times out.
If None, the default, wait forever.
@type timeout: float
@param port: The port to which to send the message. The default is 53.
@type port: int
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of where.
If the inference attempt fails, AF_INET is used.
@type af: int
@rtype: dns.message.Message object
@param source: source address. The default is the wildcard address.
@type source: string
@param source_port: The port from which to send the message.
*q*, a ``dns.message.message``, the query to send
*where*, a ``text`` containing an IPv4 or IPv6 address, where
to send the message.
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
query times out. If ``None``, the default, wait forever.
*port*, an ``int``, the port send the message to. The default is 53.
*af*, an ``int``, the address family to use. The default is ``None``,
which causes the address family to use to be inferred from the form of
*where*. If the inference attempt fails, AF_INET is used. This
parameter is historical; you need never set it.
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
the source address. The default is the wildcard address.
*source_port*, an ``int``, the port from which to send the message.
The default is 0.
@type source_port: int
@param one_rr_per_rrset: Put each RR into its own RRset
@type one_rr_per_rrset: bool
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
Returns a ``dns.message.Message``.
"""
wire = q.to_wire()
@ -336,6 +462,7 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
source, source_port)
s = socket_factory(af, socket.SOCK_STREAM, 0)
begin_time = None
received_time = None
try:
expiration = _compute_expiration(timeout)
s.setblocking(0)
@ -343,25 +470,15 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
if source is not None:
s.bind(source)
_connect(s, destination)
l = len(wire)
# copying the wire into tcpmsg is inefficient, but lets us
# avoid writev() or doing a short write that would get pushed
# onto the net
tcpmsg = struct.pack("!H", l) + wire
_net_write(s, tcpmsg, expiration)
ldata = _net_read(s, 2, expiration)
(l,) = struct.unpack("!H", ldata)
wire = _net_read(s, l, expiration)
send_tcp(s, wire, expiration)
(r, received_time) = receive_tcp(s, expiration, one_rr_per_rrset,
q.keyring, q.mac)
finally:
if begin_time is None:
if begin_time is None or received_time is None:
response_time = 0
else:
response_time = time.time() - begin_time
response_time = received_time - begin_time
s.close()
r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
one_rr_per_rrset=one_rr_per_rrset)
r.time = response_time
if not q.is_response(r):
raise BadResponse
@ -374,51 +491,59 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
use_udp=False, keyalgorithm=dns.tsig.default_algorithm):
"""Return a generator for the responses to a zone transfer.
@param where: where to send the message
@type where: string containing an IPv4 or IPv6 address
@param zone: The name of the zone to transfer
@type zone: dns.name.Name object or string
@param rdtype: The type of zone transfer. The default is
dns.rdatatype.AXFR.
@type rdtype: int or string
@param rdclass: The class of the zone transfer. The default is
dns.rdataclass.IN.
@type rdclass: int or string
@param timeout: The number of seconds to wait for each response message.
If None, the default, wait forever.
@type timeout: float
@param port: The port to which to send the message. The default is 53.
@type port: int
@param keyring: The TSIG keyring to use
@type keyring: dict
@param keyname: The name of the TSIG key to use
@type keyname: dns.name.Name object or string
@param relativize: If True, all names in the zone will be relativized to
the zone origin. It is essential that the relativize setting matches
the one specified to dns.zone.from_xfr().
@type relativize: bool
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of where.
If the inference attempt fails, AF_INET is used.
@type af: int
@param lifetime: The total number of seconds to spend doing the transfer.
If None, the default, then there is no limit on the time the transfer may
take.
@type lifetime: float
@rtype: generator of dns.message.Message objects.
@param source: source address. The default is the wildcard address.
@type source: string
@param source_port: The port from which to send the message.
*where*. If the inference attempt fails, AF_INET is used. This
parameter is historical; you need never set it.
*zone*, a ``dns.name.Name`` or ``text``, the name of the zone to transfer.
*rdtype*, an ``int`` or ``text``, the type of zone transfer. The
default is ``dns.rdatatype.AXFR``. ``dns.rdatatype.IXFR`` can be
used to do an incremental transfer instead.
*rdclass*, an ``int`` or ``text``, the class of the zone transfer.
The default is ``dns.rdataclass.IN``.
*timeout*, a ``float``, the number of seconds to wait for each
response message. If None, the default, wait forever.
*port*, an ``int``, the port send the message to. The default is 53.
*keyring*, a ``dict``, the keyring to use for TSIG.
*keyname*, a ``dns.name.Name`` or ``text``, the name of the TSIG
key to use.
*relativize*, a ``bool``. If ``True``, all names in the zone will be
relativized to the zone origin. It is essential that the
relativize setting matches the one specified to
``dns.zone.from_xfr()`` if using this generator to make a zone.
*af*, an ``int``, the address family to use. The default is ``None``,
which causes the address family to use to be inferred from the form of
*where*. If the inference attempt fails, AF_INET is used. This
parameter is historical; you need never set it.
*lifetime*, a ``float``, the total number of seconds to spend
doing the transfer. If ``None``, the default, then there is no
limit on the time the transfer may take.
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
the source address. The default is the wildcard address.
*source_port*, an ``int``, the port from which to send the message.
The default is 0.
@type source_port: int
@param serial: The SOA serial number to use as the base for an IXFR diff
sequence (only meaningful if rdtype == dns.rdatatype.IXFR).
@type serial: int
@param use_udp: Use UDP (only meaningful for IXFR)
@type use_udp: bool
@param keyalgorithm: The TSIG algorithm to use; defaults to
dns.tsig.default_algorithm
@type keyalgorithm: string
*serial*, an ``int``, the SOA serial number to use as the base for
an IXFR diff sequence (only meaningful if *rdtype* is
``dns.rdatatype.IXFR``).
*use_udp*, a ``bool``. If ``True``, use UDP (only meaningful for IXFR).
*keyalgorithm*, a ``dns.name.Name`` or ``text``, the TSIG algorithm to use.
Raises on errors, and so does the generator.
Returns a generator of ``dns.message.Message`` objects.
"""
if isinstance(zone, string_types):
@ -481,6 +606,9 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
xfr=True, origin=origin, tsig_ctx=tsig_ctx,
multi=True, first=first,
one_rr_per_rrset=is_ixfr)
rcode = r.rcode()
if rcode != dns.rcode.NOERROR:
raise TransferError(rcode)
tsig_ctx = r.tsig_ctx
first = False
answer_index = 0

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -18,18 +18,29 @@
import dns.exception
from ._compat import long
#: No error
NOERROR = 0
#: Form error
FORMERR = 1
#: Server failure
SERVFAIL = 2
#: Name does not exist ("Name Error" in RFC 1025 terminology).
NXDOMAIN = 3
#: Not implemented
NOTIMP = 4
#: Refused
REFUSED = 5
#: Name exists.
YXDOMAIN = 6
#: RRset exists.
YXRRSET = 7
#: RRset does not exist.
NXRRSET = 8
#: Not authoritative.
NOTAUTH = 9
#: Name not in zone.
NOTZONE = 10
#: Bad EDNS version.
BADVERS = 16
_by_text = {
@ -55,17 +66,17 @@ _by_value = dict((y, x) for x, y in _by_text.items())
class UnknownRcode(dns.exception.DNSException):
"""A DNS rcode is unknown."""
def from_text(text):
"""Convert text into an rcode.
@param text: the textual rcode
@type text: string
@raises UnknownRcode: the rcode is unknown
@rtype: int
*text*, a ``text``, the textual rcode or an integer in textual form.
Raises ``dns.rcode.UnknownRcode`` if the rcode mnemonic is unknown.
Returns an ``int``.
"""
if text.isdigit():
@ -81,12 +92,13 @@ def from_text(text):
def from_flags(flags, ednsflags):
"""Return the rcode value encoded by flags and ednsflags.
@param flags: the DNS flags
@type flags: int
@param ednsflags: the EDNS flags
@type ednsflags: int
@raises ValueError: rcode is < 0 or > 4095
@rtype: int
*flags*, an ``int``, the DNS flags field.
*ednsflags*, an ``int``, the EDNS flags field.
Raises ``ValueError`` if rcode is < 0 or > 4095
Returns an ``int``.
"""
value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0)
@ -98,10 +110,11 @@ def from_flags(flags, ednsflags):
def to_flags(value):
"""Return a (flags, ednsflags) tuple which encodes the rcode.
@param value: the rcode
@type value: int
@raises ValueError: rcode is < 0 or > 4095
@rtype: (int, int) tuple
*value*, an ``int``, the rcode.
Raises ``ValueError`` if rcode is < 0 or > 4095.
Returns an ``(int, int)`` tuple.
"""
if value < 0 or value > 4095:
@ -114,11 +127,15 @@ def to_flags(value):
def to_text(value):
"""Convert rcode into text.
@param value: the rcode
@type value: int
@rtype: string
*value*, and ``int``, the rcode.
Raises ``ValueError`` if rcode is < 0 or > 4095.
Returns a ``text``.
"""
if value < 0 or value > 4095:
raise ValueError('rcode must be >= 0 and <= 4095')
text = _by_value.get(value)
if text is None:
text = str(value)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,17 +13,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS rdata.
@var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
the module which implements that type.
@type _rdata_modules: dict
@var _module_prefix: The prefix to use when forming modules names. The
default is 'dns.rdtypes'. Changing this value will break the library.
@type _module_prefix: string
@var _hex_chunk: At most this many octets that will be represented in each
chunk of hexstring that _hexify() produces before whitespace occurs.
@type _hex_chunk: int"""
"""DNS rdata."""
from io import BytesIO
import base64
@ -42,12 +32,7 @@ _hex_chunksize = 32
def _hexify(data, chunksize=_hex_chunksize):
"""Convert a binary string into its hex encoding, broken up into chunks
of I{chunksize} characters separated by a space.
@param data: the binary string
@type data: string
@param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
@rtype: string
of chunksize characters separated by a space.
"""
line = binascii.hexlify(data)
@ -60,13 +45,7 @@ _base64_chunksize = 32
def _base64ify(data, chunksize=_base64_chunksize):
"""Convert a binary string into its base64 encoding, broken up into chunks
of I{chunksize} characters separated by a space.
@param data: the binary string
@type data: string
@param chunksize: the chunk size. Default is
L{dns.rdata._base64_chunksize}
@rtype: string
of chunksize characters separated by a space.
"""
line = base64.b64encode(data)
@ -77,13 +56,7 @@ def _base64ify(data, chunksize=_base64_chunksize):
__escaped = bytearray(b'"\\')
def _escapify(qstring):
"""Escape the characters in a quoted string which need it.
@param qstring: the string
@type qstring: string
@returns: the escaped string
@rtype: string
"""
"""Escape the characters in a quoted string which need it."""
if isinstance(qstring, text_type):
qstring = qstring.encode()
@ -104,10 +77,6 @@ def _escapify(qstring):
def _truncate_bitmap(what):
"""Determine the index of greatest byte that isn't all zeros, and
return the bitmap that contains all the bytes less than that index.
@param what: a string of octets representing a bitmap.
@type what: string
@rtype: string
"""
for i in xrange(len(what) - 1, -1, -1):
@ -117,30 +86,30 @@ def _truncate_bitmap(what):
class Rdata(object):
"""Base class for all DNS rdata types.
"""
"""Base class for all DNS rdata types."""
__slots__ = ['rdclass', 'rdtype']
def __init__(self, rdclass, rdtype):
"""Initialize an rdata.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
*rdclass*, an ``int`` is the rdataclass of the Rdata.
*rdtype*, an ``int`` is the rdatatype of the Rdata.
"""
self.rdclass = rdclass
self.rdtype = rdtype
def covers(self):
"""DNS SIG/RRSIG rdatas apply to a specific type; this type is
"""Return the type a Rdata covers.
DNS SIG/RRSIG rdatas apply to a specific type; this type is
returned by the covers() function. If the rdata type is not
SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
creating rdatasets, allowing the rdataset to contain only RRSIGs
of a particular type, e.g. RRSIG(NS).
@rtype: int
Returns an ``int``.
"""
return dns.rdatatype.NONE
@ -149,37 +118,52 @@ class Rdata(object):
"""Return a 32-bit type value, the least significant 16 bits of
which are the ordinary DNS type, and the upper 16 bits of which are
the "covered" type, if any.
@rtype: int
Returns an ``int``.
"""
return self.covers() << 16 | self.rdtype
def to_text(self, origin=None, relativize=True, **kw):
"""Convert an rdata to text format.
@rtype: string
Returns a ``text``.
"""
raise NotImplementedError
def to_wire(self, file, compress=None, origin=None):
"""Convert an rdata to wire format.
@rtype: string
Returns a ``binary``.
"""
raise NotImplementedError
def to_digestable(self, origin=None):
"""Convert rdata to a format suitable for digesting in hashes. This
is also the DNSSEC canonical form."""
is also the DNSSEC canonical form.
Returns a ``binary``.
"""
f = BytesIO()
self.to_wire(f, None, origin)
return f.getvalue()
def validate(self):
"""Check that the current contents of the rdata's fields are
valid. If you change an rdata by assigning to its fields,
valid.
If you change an rdata by assigning to its fields,
it is a good idea to call validate() when you are done making
changes.
Raises various exceptions if there are problems.
Returns ``None``.
"""
dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
def __repr__(self):
@ -197,17 +181,20 @@ class Rdata(object):
def _cmp(self, other):
"""Compare an rdata with another rdata of the same rdtype and
rdclass. Return < 0 if self < other in the DNSSEC ordering,
0 if self == other, and > 0 if self > other.
rdclass.
Return < 0 if self < other in the DNSSEC ordering, 0 if self
== other, and > 0 if self > other.
"""
our = self.to_digestable(dns.name.root)
their = other.to_digestable(dns.name.root)
if our == their:
return 0
if our > their:
elif our > their:
return 1
return -1
else:
return -1
def __eq__(self, other):
if not isinstance(other, Rdata):
@ -253,42 +240,10 @@ class Rdata(object):
@classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
"""Build an rdata object from text format.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param tok: The tokenizer
@type tok: dns.tokenizer.Tokenizer
@param origin: The origin to use for relative names
@type origin: dns.name.Name
@param relativize: should names be relativized?
@type relativize: bool
@rtype: dns.rdata.Rdata instance
"""
raise NotImplementedError
@classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
"""Build an rdata object from wire format
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param wire: The wire-format message
@type wire: string
@param current: The offset in wire of the beginning of the rdata.
@type current: int
@param rdlen: The length of the wire-format rdata
@type rdlen: int
@param origin: The origin to use for relative names
@type origin: dns.name.Name
@rtype: dns.rdata.Rdata instance
"""
raise NotImplementedError
def choose_relativity(self, origin=None, relativize=True):
@ -301,7 +256,7 @@ class Rdata(object):
class GenericRdata(Rdata):
"""Generate Rdata Class
"""Generic Rdata Class
This class is used for rdata types for which we have no better
implementation. It implements the DNS "unknown RRs" scheme.
@ -392,20 +347,23 @@ def from_text(rdclass, rdtype, tok, origin=None, relativize=True):
Once a class is chosen, its from_text() class method is called
with the parameters to this function.
If I{tok} is a string, then a tokenizer is created and the string
If *tok* is a ``text``, then a tokenizer is created and the string
is used as its input.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param tok: The tokenizer or input text
@type tok: dns.tokenizer.Tokenizer or string
@param origin: The origin to use for relative names
@type origin: dns.name.Name
@param relativize: Should names be relativized?
@type relativize: bool
@rtype: dns.rdata.Rdata instance"""
*rdclass*, an ``int``, the rdataclass.
*rdtype*, an ``int``, the rdatatype.
*tok*, a ``dns.tokenizer.Tokenizer`` or a ``text``.
*origin*, a ``dns.name.Name`` (or ``None``), the
origin to use for relative names.
*relativize*, a ``bool``. If true, name will be relativized to
the specified origin.
Returns an instance of the chosen Rdata subclass.
"""
if isinstance(tok, string_types):
tok = dns.tokenizer.Tokenizer(tok)
@ -439,19 +397,22 @@ def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
Once a class is chosen, its from_wire() class method is called
with the parameters to this function.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param wire: The wire-format message
@type wire: string
@param current: The offset in wire of the beginning of the rdata.
@type current: int
@param rdlen: The length of the wire-format rdata
@type rdlen: int
@param origin: The origin to use for relative names
@type origin: dns.name.Name
@rtype: dns.rdata.Rdata instance"""
*rdclass*, an ``int``, the rdataclass.
*rdtype*, an ``int``, the rdatatype.
*wire*, a ``binary``, the wire-format message.
*current*, an ``int``, the offset in wire of the beginning of
the rdata.
*rdlen*, an ``int``, the length of the wire-format rdata
*origin*, a ``dns.name.Name`` (or ``None``). If not ``None``,
then names will be relativized to this origin.
Returns an instance of the chosen Rdata subclass.
"""
wire = dns.wiredata.maybe_wrap(wire)
cls = get_rdata_class(rdclass, rdtype)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,15 +13,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Rdata Classes.
@var _by_text: The rdata class textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata class value to textual name mapping
@type _by_value: dict
@var _metaclasses: If an rdataclass is a metaclass, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metaclasses: dict"""
"""DNS Rdata Classes."""
import re
@ -67,17 +59,22 @@ _unknown_class_pattern = re.compile('CLASS([0-9]+)$', re.I)
class UnknownRdataclass(dns.exception.DNSException):
"""A DNS class is unknown."""
def from_text(text):
"""Convert text into a DNS rdata class value.
@param text: the text
@type text: string
@rtype: int
@raises dns.rdataclass.UnknownRdataclass: the class is unknown
@raises ValueError: the rdata class value is not >= 0 and <= 65535
The input text can be a defined DNS RR class mnemonic or
instance of the DNS generic class syntax.
For example, "IN" and "CLASS1" will both result in a value of 1.
Raises ``dns.rdatatype.UnknownRdataclass`` if the class is unknown.
Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535.
Returns an ``int``.
"""
value = _by_text.get(text.upper())
@ -92,11 +89,14 @@ def from_text(text):
def to_text(value):
"""Convert a DNS rdata class to text.
@param value: the rdata class value
@type value: int
@rtype: string
@raises ValueError: the rdata class value is not >= 0 and <= 65535
"""Convert a DNS rdata type value to text.
If the value has a known mnemonic, it will be used, otherwise the
DNS generic class syntax will be used.
Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535.
Returns a ``str``.
"""
if value < 0 or value > 65535:
@ -108,10 +108,12 @@ def to_text(value):
def is_metaclass(rdclass):
"""True if the class is a metaclass.
@param rdclass: the rdata class
@type rdclass: int
@rtype: bool"""
"""True if the specified class is a metaclass.
The currently defined metaclasses are ANY and NONE.
*rdclass* is an ``int``.
"""
if rdclass in _metaclasses:
return True

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -31,50 +31,37 @@ SimpleSet = dns.set.Set
class DifferingCovers(dns.exception.DNSException):
"""An attempt was made to add a DNS SIG/RRSIG whose covered type
is not the same as that of the other rdatas in the rdataset."""
class IncompatibleTypes(dns.exception.DNSException):
"""An attempt was made to add DNS RR data of an incompatible type."""
class Rdataset(dns.set.Set):
"""A DNS rdataset.
@ivar rdclass: The class of the rdataset
@type rdclass: int
@ivar rdtype: The type of the rdataset
@type rdtype: int
@ivar covers: The covered type. Usually this value is
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
types as if they were a family of
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
@type covers: int
@ivar ttl: The DNS TTL (Time To Live) value
@type ttl: int
"""
"""A DNS rdataset."""
__slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE, ttl=0):
"""Create a new rdataset of the specified class and type.
@see: the description of the class instance variables for the
meaning of I{rdclass} and I{rdtype}"""
*rdclass*, an ``int``, the rdataclass.
*rdtype*, an ``int``, the rdatatype.
*covers*, an ``int``, the covered rdatatype.
*ttl*, an ``int``, the TTL.
"""
super(Rdataset, self).__init__()
self.rdclass = rdclass
self.rdtype = rdtype
self.covers = covers
self.ttl = 0
self.ttl = ttl
def _clone(self):
obj = super(Rdataset, self)._clone()
@ -85,11 +72,14 @@ class Rdataset(dns.set.Set):
return obj
def update_ttl(self, ttl):
"""Set the TTL of the rdataset to be the lesser of the set's current
"""Perform TTL minimization.
Set the TTL of the rdataset to be the lesser of the set's current
TTL or the specified TTL. If the set contains no rdatas, set the TTL
to the specified TTL.
@param ttl: The TTL
@type ttl: int"""
*ttl*, an ``int``.
"""
if len(self) == 0:
self.ttl = ttl
@ -99,13 +89,19 @@ class Rdataset(dns.set.Set):
def add(self, rd, ttl=None):
"""Add the specified rdata to the rdataset.
If the optional I{ttl} parameter is supplied, then
self.update_ttl(ttl) will be called prior to adding the rdata.
If the optional *ttl* parameter is supplied, then
``self.update_ttl(ttl)`` will be called prior to adding the rdata.
@param rd: The rdata
@type rd: dns.rdata.Rdata object
@param ttl: The TTL
@type ttl: int"""
*rd*, a ``dns.rdata.Rdata``, the rdata
*ttl*, an ``int``, the TTL.
Raises ``dns.rdataset.IncompatibleTypes`` if the type and class
do not match the type and class of the rdataset.
Raises ``dns.rdataset.DifferingCovers`` if the type is a signature
type and the covered type does not match that of the rdataset.
"""
#
# If we're adding a signature, do some special handling to
@ -139,8 +135,9 @@ class Rdataset(dns.set.Set):
def update(self, other):
"""Add all rdatas in other to self.
@param other: The rdataset from which to update
@type other: dns.rdataset.Rdataset object"""
*other*, a ``dns.rdataset.Rdataset``, the rdataset from which
to update.
"""
self.update_ttl(other.ttl)
super(Rdataset, self).update(other)
@ -157,10 +154,6 @@ class Rdataset(dns.set.Set):
return self.to_text()
def __eq__(self, other):
"""Two rdatasets are equal if they have the same class, type, and
covers, and contain the same rdata.
@rtype: bool"""
if not isinstance(other, Rdataset):
return False
if self.rdclass != other.rdclass or \
@ -176,20 +169,23 @@ class Rdataset(dns.set.Set):
override_rdclass=None, **kw):
"""Convert the rdataset into DNS master file format.
@see: L{dns.name.Name.choose_relativity} for more information
on how I{origin} and I{relativize} determine the way names
See ``dns.name.Name.choose_relativity`` for more information
on how *origin* and *relativize* determine the way names
are emitted.
Any additional keyword arguments are passed on to the rdata
to_text() method.
``to_text()`` method.
*name*, a ``dns.name.Name``. If name is not ``None``, emit RRs with
*name* as the owner name.
*origin*, a ``dns.name.Name`` or ``None``, the origin for relative
names.
*relativize*, a ``bool``. If ``True``, names will be relativized
to *origin*.
"""
@param name: If name is not None, emit a RRs with I{name} as
the owner name.
@type name: dns.name.Name object
@param origin: The origin for relative names, or None.
@type origin: dns.name.Name object
@param relativize: True if names should names be relativized
@type relativize: bool"""
if name is not None:
name = name.choose_relativity(origin, relativize)
ntext = str(name)
@ -227,16 +223,26 @@ class Rdataset(dns.set.Set):
override_rdclass=None, want_shuffle=True):
"""Convert the rdataset to wire format.
@param name: The owner name of the RRset that will be emitted
@type name: dns.name.Name object
@param file: The file to which the wire format data will be appended
@type file: file
@param compress: The compression table to use; the default is None.
@type compress: dict
@param origin: The origin to be appended to any relative names when
they are emitted. The default is None.
@returns: the number of records emitted
@rtype: int
*name*, a ``dns.name.Name`` is the owner name to use.
*file* is the file where the name is emitted (typically a
BytesIO file).
*compress*, a ``dict``, is the compression table to use. If
``None`` (the default), names will not be compressed.
*origin* is a ``dns.name.Name`` or ``None``. If the name is
relative and origin is not ``None``, then *origin* will be appended
to it.
*override_rdclass*, an ``int``, is used as the class instead of the
class of the rdataset. This is useful when rendering rdatasets
associated with dynamic updates.
*want_shuffle*, a ``bool``. If ``True``, then the order of the
Rdatas within the Rdataset will be shuffled before rendering.
Returns an ``int``, the number of records emitted.
"""
if override_rdclass is not None:
@ -272,8 +278,9 @@ class Rdataset(dns.set.Set):
return len(self)
def match(self, rdclass, rdtype, covers):
"""Returns True if this rdataset matches the specified class, type,
and covers"""
"""Returns ``True`` if this rdataset matches the specified class,
type, and covers.
"""
if self.rdclass == rdclass and \
self.rdtype == rdtype and \
self.covers == covers:
@ -285,7 +292,7 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas):
"""Create an rdataset with the specified class, type, and TTL, and with
the specified list of rdatas in text format.
@rtype: dns.rdataset.Rdataset object
Returns a ``dns.rdataset.Rdataset`` object.
"""
if isinstance(rdclass, string_types):
@ -304,7 +311,7 @@ def from_text(rdclass, rdtype, ttl, *text_rdatas):
"""Create an rdataset with the specified class, type, and TTL, and with
the specified rdatas in text format.
@rtype: dns.rdataset.Rdataset object
Returns a ``dns.rdataset.Rdataset`` object.
"""
return from_text_list(rdclass, rdtype, ttl, text_rdatas)
@ -314,7 +321,7 @@ def from_rdata_list(ttl, rdatas):
"""Create an rdataset with the specified TTL, and with
the specified list of rdata objects.
@rtype: dns.rdataset.Rdataset object
Returns a ``dns.rdataset.Rdataset`` object.
"""
if len(rdatas) == 0:
@ -332,7 +339,7 @@ def from_rdata(ttl, *rdatas):
"""Create an rdataset with the specified TTL, and with
the specified rdata objects.
@rtype: dns.rdataset.Rdataset object
Returns a ``dns.rdataset.Rdataset`` object.
"""
return from_rdata_list(ttl, rdatas)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,18 +13,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Rdata Types.
@var _by_text: The rdata type textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata type value to textual name mapping
@type _by_value: dict
@var _metatypes: If an rdatatype is a metatype, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metatypes: dict
@var _singletons: If an rdatatype is a singleton, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _singletons: dict"""
"""DNS Rdata Types."""
import re
@ -82,6 +71,7 @@ TLSA = 52
HIP = 55
CDS = 59
CDNSKEY = 60
OPENPGPKEY = 61
CSYNC = 62
SPF = 99
UNSPEC = 103
@ -153,6 +143,7 @@ _by_text = {
'HIP': HIP,
'CDS': CDS,
'CDNSKEY': CDNSKEY,
'OPENPGPKEY': OPENPGPKEY,
'CSYNC': CSYNC,
'SPF': SPF,
'UNSPEC': UNSPEC,
@ -178,7 +169,6 @@ _by_text = {
_by_value = dict((y, x) for x, y in _by_text.items())
_metatypes = {
OPT: True
}
@ -188,24 +178,30 @@ _singletons = {
NXT: True,
DNAME: True,
NSEC: True,
# CNAME is technically a singleton, but we allow multiple CNAMEs.
CNAME: True,
}
_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I)
class UnknownRdatatype(dns.exception.DNSException):
"""DNS resource record type is unknown."""
def from_text(text):
"""Convert text into a DNS rdata type value.
@param text: the text
@type text: string
@raises dns.rdatatype.UnknownRdatatype: the type is unknown
@raises ValueError: the rdata type value is not >= 0 and <= 65535
@rtype: int"""
The input text can be a defined DNS RR type mnemonic or
instance of the DNS generic type syntax.
For example, "NS" and "TYPE2" will both result in a value of 2.
Raises ``dns.rdatatype.UnknownRdatatype`` if the type is unknown.
Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535.
Returns an ``int``.
"""
value = _by_text.get(text.upper())
if value is None:
@ -219,11 +215,15 @@ def from_text(text):
def to_text(value):
"""Convert a DNS rdata type to text.
@param value: the rdata type value
@type value: int
@raises ValueError: the rdata type value is not >= 0 and <= 65535
@rtype: string"""
"""Convert a DNS rdata type value to text.
If the value has a known mnemonic, it will be used, otherwise the
DNS generic type syntax will be used.
Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535.
Returns a ``str``.
"""
if value < 0 or value > 65535:
raise ValueError("type must be between >= 0 and <= 65535")
@ -234,10 +234,15 @@ def to_text(value):
def is_metatype(rdtype):
"""True if the type is a metatype.
@param rdtype: the type
@type rdtype: int
@rtype: bool"""
"""True if the specified type is a metatype.
*rdtype* is an ``int``.
The currently defined metatypes are TKEY, TSIG, IXFR, AXFR, MAILA,
MAILB, ANY, and OPT.
Returns a ``bool``.
"""
if rdtype >= TKEY and rdtype <= ANY or rdtype in _metatypes:
return True
@ -245,10 +250,18 @@ def is_metatype(rdtype):
def is_singleton(rdtype):
"""True if the type is a singleton.
@param rdtype: the type
@type rdtype: int
@rtype: bool"""
"""Is the specified type a singleton type?
Singleton types can only have a single rdata in an rdataset, or a single
RR in an RRset.
The currently defined singleton types are CNAME, DNAME, NSEC, NXT, and
SOA.
*rdtype* is an ``int``.
Returns a ``bool``.
"""
if rdtype in _singletons:
return True

View File

@ -0,0 +1,58 @@
# 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 base64
import dns.exception
import dns.rdata
import dns.tokenizer
class OPENPGPKEY(dns.rdata.Rdata):
"""OPENPGPKEY record
@ivar key: the key
@type key: bytes
@see: RFC 7929
"""
def __init__(self, rdclass, rdtype, key):
super(OPENPGPKEY, self).__init__(rdclass, rdtype)
self.key = key
def to_text(self, origin=None, relativize=True, **kw):
return dns.rdata._base64ify(self.key)
@classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
chunks = []
while 1:
t = tok.get().unescape()
if t.is_eol_or_eof():
break
if not t.is_identifier():
raise dns.exception.SyntaxError
chunks.append(t.value.encode())
b64 = b''.join(chunks)
key = base64.b64decode(b64)
return cls(rdclass, rdtype, key)
def to_wire(self, file, compress=None, origin=None):
file.write(self.key)
@classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
key = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, key)

View File

@ -17,10 +17,13 @@
__all__ = [
'AFSDB',
'AVC',
'CAA',
'CDNSKEY',
'CDS',
'CERT',
'CNAME',
'CSYNC',
'DLV',
'DNAME',
'DNSKEY',
@ -37,7 +40,7 @@ __all__ = [
'NSEC',
'NSEC3',
'NSEC3PARAM',
'TLSA',
'OPENPGPKEY',
'PTR',
'RP',
'RRSIG',
@ -45,6 +48,8 @@ __all__ = [
'SOA',
'SPF',
'SSHFP',
'TLSA',
'TXT',
'URI',
'X25',
]

View File

@ -48,5 +48,5 @@ class A(dns.rdata.Rdata):
@classmethod
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])
return cls(rdclass, rdtype, address)

View File

@ -20,7 +20,7 @@ import dns.exception
import dns.inet
import dns.rdata
import dns.tokenizer
from dns._compat import xrange
from dns._compat import xrange, maybe_chr
class APLItem(object):
@ -63,7 +63,7 @@ class APLItem(object):
#
last = 0
for i in xrange(len(address) - 1, -1, -1):
if address[i] != chr(0):
if address[i] != maybe_chr(0):
last = i + 1
break
address = address[0: last]
@ -142,11 +142,11 @@ class APL(dns.rdata.Rdata):
l = len(address)
if header[0] == 1:
if l < 4:
address += '\x00' * (4 - l)
address += b'\x00' * (4 - l)
address = dns.inet.inet_ntop(dns.inet.AF_INET, address)
elif header[0] == 2:
if l < 16:
address += '\x00' * (16 - l)
address += b'\x00' * (16 - l)
address = dns.inet.inet_ntop(dns.inet.AF_INET6, address)
else:
#

View File

@ -20,6 +20,7 @@ __all__ = [
'AAAA',
'APL',
'DHCID',
'IPSECKEY',
'KX',
'NAPTR',
'NSAP',

View File

@ -1,4 +1,4 @@
# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2006-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -20,24 +20,29 @@ import struct
import dns.exception
import dns.rdata
import dns.tokenizer
from dns._compat import binary_type
from dns._compat import binary_type, string_types
class TXTBase(dns.rdata.Rdata):
"""Base class for rdata that is like a TXT record
@ivar strings: the text strings
@type strings: list of string
@ivar strings: the strings
@type strings: list of binary
@see: RFC 1035"""
__slots__ = ['strings']
def __init__(self, rdclass, rdtype, strings):
super(TXTBase, self).__init__(rdclass, rdtype)
if isinstance(strings, str):
if isinstance(strings, binary_type) or \
isinstance(strings, string_types):
strings = [strings]
self.strings = strings[:]
self.strings = []
for string in strings:
if isinstance(string, string_types):
string = string.encode()
self.strings.append(string)
def to_text(self, origin=None, relativize=True, **kw):
txt = ''

View File

@ -1,4 +1,4 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2001-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -32,7 +32,6 @@ ADDITIONAL = 3
class Renderer(object):
"""Helper class for building DNS wire-format messages.
Most applications can use the higher-level L{dns.message.Message}
@ -54,41 +53,27 @@ class Renderer(object):
r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac)
wire = r.get_wire()
@ivar output: where rendering is written
@type output: BytesIO object
@ivar id: the message id
@type id: int
@ivar flags: the message flags
@type flags: int
@ivar max_size: the maximum size of the message
@type max_size: int
@ivar origin: the origin to use when rendering relative names
@type origin: dns.name.Name object
@ivar compress: the compression table
@type compress: dict
@ivar section: the section currently being rendered
@type section: int (dns.renderer.QUESTION, dns.renderer.ANSWER,
dns.renderer.AUTHORITY, or dns.renderer.ADDITIONAL)
@ivar counts: list of the number of RRs in each section
@type counts: int list of length 4
@ivar mac: the MAC of the rendered message (if TSIG was used)
@type mac: string
output, a BytesIO, where rendering is written
id: the message id
flags: the message flags
max_size: the maximum size of the message
origin: the origin to use when rendering relative names
compress: the compression table
section: an int, the section currently being rendered
counts: list of the number of RRs in each section
mac: the MAC of the rendered message (if TSIG was used)
"""
def __init__(self, id=None, flags=0, max_size=65535, origin=None):
"""Initialize a new renderer.
@param id: the message id
@type id: int
@param flags: the DNS message flags
@type flags: int
@param max_size: the maximum message size; the default is 65535.
If rendering results in a message greater than I{max_size},
then L{dns.exception.TooBig} will be raised.
@type max_size: int
@param origin: the origin to use when rendering relative names
@type origin: dns.name.Name or None.
"""
"""Initialize a new renderer."""
self.output = BytesIO()
if id is None:
@ -105,12 +90,9 @@ class Renderer(object):
self.mac = ''
def _rollback(self, where):
"""Truncate the output buffer at offset I{where}, and remove any
"""Truncate the output buffer at offset *where*, and remove any
compression table entries that pointed beyond the truncation
point.
@param where: the offset
@type where: int
"""
self.output.seek(where)
@ -128,9 +110,7 @@ class Renderer(object):
Sections must be rendered order: QUESTION, ANSWER, AUTHORITY,
ADDITIONAL. Sections may be empty.
@param section: the section
@type section: int
@raises dns.exception.FormError: an attempt was made to set
Raises dns.exception.FormError if an attempt was made to set
a section value less than the current section.
"""
@ -140,15 +120,7 @@ class Renderer(object):
self.section = section
def add_question(self, qname, rdtype, rdclass=dns.rdataclass.IN):
"""Add a question to the message.
@param qname: the question name
@type qname: dns.name.Name
@param rdtype: the question rdata type
@type rdtype: int
@param rdclass: the question rdata class
@type rdclass: int
"""
"""Add a question to the message."""
self._set_section(QUESTION)
before = self.output.tell()
@ -165,11 +137,6 @@ class Renderer(object):
Any keyword arguments are passed on to the rdataset's to_wire()
routine.
@param section: the section
@type section: int
@param rrset: the rrset
@type rrset: dns.rrset.RRset object
"""
self._set_section(section)
@ -187,13 +154,6 @@ class Renderer(object):
Any keyword arguments are passed on to the rdataset's to_wire()
routine.
@param section: the section
@type section: int
@param name: the owner name
@type name: dns.name.Name object
@param rdataset: the rdataset
@type rdataset: dns.rdataset.Rdataset object
"""
self._set_section(section)
@ -207,19 +167,7 @@ class Renderer(object):
self.counts[section] += n
def add_edns(self, edns, ednsflags, payload, options=None):
"""Add an EDNS OPT record to the message.
@param edns: The EDNS level to use.
@type edns: int
@param ednsflags: EDNS flag values.
@type ednsflags: int
@param payload: The EDNS sender's payload field, which is the maximum
size of UDP datagram the sender can handle.
@type payload: int
@param options: The EDNS options list
@type options: list of dns.edns.Option instances
@see: RFC 2671
"""
"""Add an EDNS OPT record to the message."""
# make sure the EDNS version in ednsflags agrees with edns
ednsflags &= long(0xFF00FFFF)
@ -255,26 +203,7 @@ class Renderer(object):
def add_tsig(self, keyname, secret, fudge, id, tsig_error, other_data,
request_mac, algorithm=dns.tsig.default_algorithm):
"""Add a TSIG signature to the message.
@param keyname: the TSIG key name
@type keyname: dns.name.Name object
@param secret: the secret to use
@type secret: string
@param fudge: TSIG time fudge
@type fudge: int
@param id: the message id to encode in the tsig signature
@type id: int
@param tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@param other_data: TSIG other data.
@type other_data: string
@param request_mac: This message is a response to the request which
had the specified MAC.
@type request_mac: string
@param algorithm: the TSIG algorithm to use
@type algorithm: dns.name.Name object
"""
"""Add a TSIG signature to the message."""
self._set_section(ADDITIONAL)
before = self.output.tell()
@ -321,9 +250,6 @@ class Renderer(object):
self.output.seek(0, 2)
def get_wire(self):
"""Return the wire format message.
@rtype: string
"""
"""Return the wire format message."""
return self.output.getvalue()

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,10 +13,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS stub resolver.
@var default_resolver: The default resolver object
@type default_resolver: dns.resolver.Resolver object"""
"""DNS stub resolver."""
import socket
import sys
@ -49,7 +46,6 @@ if sys.platform == 'win32':
import _winreg # pylint: disable=import-error
class NXDOMAIN(dns.exception.DNSException):
"""The DNS query name does not exist."""
supp_kwargs = set(['qnames', 'responses'])
fmt = None # we have our own __str__ implementation
@ -107,9 +103,30 @@ class NXDOMAIN(dns.exception.DNSException):
responses0[qname1] = responses1[qname1]
return NXDOMAIN(qnames=qnames0, responses=responses0)
def qnames(self):
"""All of the names that were tried.
Returns a list of ``dns.name.Name``.
"""
return self.kwargs['qnames']
def responses(self):
"""A map from queried names to their NXDOMAIN responses.
Returns a dict mapping a ``dns.name.Name`` to a
``dns.message.Message``.
"""
return self.kwargs['responses']
def response(self, qname):
"""The response for query *qname*.
Returns a ``dns.message.Message``.
"""
return self.kwargs['responses'][qname]
class YXDOMAIN(dns.exception.DNSException):
"""The DNS query name is too long after DNAME substitution."""
# The definition of the Timeout exception has moved from here to the
@ -120,7 +137,6 @@ Timeout = dns.exception.Timeout
class NoAnswer(dns.exception.DNSException):
"""The DNS response does not contain an answer to the question."""
fmt = 'The DNS response does not contain an answer ' + \
'to the question: {query}'
@ -132,12 +148,11 @@ class NoAnswer(dns.exception.DNSException):
class NoNameservers(dns.exception.DNSException):
"""All nameservers failed to answer the query.
errors: list of servers and respective errors
The type of errors is
[(server ip address, any object convertible to string)].
[(server IP address, any object convertible to string)].
Non-empty errors list will add explanatory message ()
"""
@ -155,50 +170,31 @@ class NoNameservers(dns.exception.DNSException):
class NotAbsolute(dns.exception.DNSException):
"""An absolute domain name is required but a relative name was provided."""
class NoRootSOA(dns.exception.DNSException):
"""There is no SOA RR at the DNS root name. This should never happen!"""
class NoMetaqueries(dns.exception.DNSException):
"""DNS metaqueries are not allowed."""
class Answer(object):
"""DNS stub resolver answer
"""DNS stub resolver answer.
Instances of this class bundle up the result of a successful DNS
resolution.
For convenience, the answer object implements much of the sequence
protocol, forwarding to its rrset. E.g. "for a in answer" is
equivalent to "for a in answer.rrset", "answer[i]" is equivalent
to "answer.rrset[i]", and "answer[i:j]" is equivalent to
"answer.rrset[i:j]".
protocol, forwarding to its ``rrset`` attribute. E.g.
``for a in answer`` is equivalent to ``for a in answer.rrset``.
``answer[i]`` is equivalent to ``answer.rrset[i]``, and
``answer[i:j]`` is equivalent to ``answer.rrset[i:j]``.
Note that CNAMEs or DNAMEs in the response may mean that answer
node's name might not be the query name.
@ivar qname: The query name
@type qname: dns.name.Name object
@ivar rdtype: The query type
@type rdtype: int
@ivar rdclass: The query class
@type rdclass: int
@ivar response: The response message
@type response: dns.message.Message object
@ivar rrset: The answer
@type rrset: dns.rrset.RRset object
@ivar expiration: The time when the answer expires
@type expiration: float (seconds since the epoch)
@ivar canonical_name: The canonical name of the query name
@type canonical_name: dns.name.Name object
RRset's name might not be the query name.
"""
def __init__(self, qname, rdtype, rdclass, response,
@ -278,32 +274,22 @@ class Answer(object):
return self.rrset and iter(self.rrset) or iter(tuple())
def __getitem__(self, i):
if self.rrset is None:
raise IndexError
return self.rrset[i]
def __delitem__(self, i):
if self.rrset is None:
raise IndexError
del self.rrset[i]
class Cache(object):
"""Simple DNS answer cache.
@ivar data: A dictionary of cached data
@type data: dict
@ivar cleaning_interval: The number of seconds between cleanings. The
default is 300 (5 minutes).
@type cleaning_interval: float
@ivar next_cleaning: The time the cache should next be cleaned (in seconds
since the epoch.)
@type next_cleaning: float
"""
"""Simple thread-safe DNS answer cache."""
def __init__(self, cleaning_interval=300.0):
"""Initialize a DNS cache.
@param cleaning_interval: the number of seconds between periodic
cleanings. The default is 300.0
@type cleaning_interval: float.
"""*cleaning_interval*, a ``float`` is the number of seconds between
periodic cleanings.
"""
self.data = {}
@ -326,12 +312,14 @@ class Cache(object):
self.next_cleaning = now + self.cleaning_interval
def get(self, key):
"""Get the answer associated with I{key}. Returns None if
no answer is cached for the key.
@param key: the key
@type key: (dns.name.Name, int, int) tuple whose values are the
query name, rdtype, and rdclass.
@rtype: dns.resolver.Answer object or None
"""Get the answer associated with *key*.
Returns None if no answer is cached for the key.
*key*, a ``(dns.name.Name, int, int)`` tuple whose values are the
query name, rdtype, and rdclass respectively.
Returns a ``dns.resolver.Answer`` or ``None``.
"""
try:
@ -346,11 +334,11 @@ class Cache(object):
def put(self, key, value):
"""Associate key and value in the cache.
@param key: the key
@type key: (dns.name.Name, int, int) tuple whose values are the
query name, rdtype, and rdclass.
@param value: The answer being cached
@type value: dns.resolver.Answer object
*key*, a ``(dns.name.Name, int, int)`` tuple whose values are the
query name, rdtype, and rdclass respectively.
*value*, a ``dns.resolver.Answer``, the answer.
"""
try:
@ -363,11 +351,11 @@ class Cache(object):
def flush(self, key=None):
"""Flush the cache.
If I{key} is specified, only that item is flushed. Otherwise
If *key* is not ``None``, only that item is flushed. Otherwise
the entire cache is flushed.
@param key: the key to flush
@type key: (dns.name.Name, int, int) tuple or None
*key*, a ``(dns.name.Name, int, int)`` tuple whose values are the
query name, rdtype, and rdclass respectively.
"""
try:
@ -383,9 +371,7 @@ class Cache(object):
class LRUCacheNode(object):
"""LRUCache node.
"""
"""LRUCache node."""
def __init__(self, key, value):
self.key = key
@ -411,30 +397,20 @@ class LRUCacheNode(object):
class LRUCache(object):
"""Bounded least-recently-used DNS answer cache.
"""Thread-safe, bounded, least-recently-used DNS answer cache.
This cache is better than the simple cache (above) if you're
running a web crawler or other process that does a lot of
resolutions. The LRUCache has a maximum number of nodes, and when
it is full, the least-recently used node is removed to make space
for a new one.
@ivar data: A dictionary of cached data
@type data: dict
@ivar sentinel: sentinel node for circular doubly linked list of nodes
@type sentinel: LRUCacheNode object
@ivar max_size: The maximum number of nodes
@type max_size: int
"""
def __init__(self, max_size=100000):
"""Initialize a DNS cache.
@param max_size: The maximum number of nodes to cache; the default is
100,000. Must be greater than 1.
@type max_size: int
"""*max_size*, an ``int``, is the maximum number of nodes to cache;
it must be greater than 0.
"""
self.data = {}
self.set_max_size(max_size)
self.sentinel = LRUCacheNode(None, None)
@ -446,13 +422,16 @@ class LRUCache(object):
self.max_size = max_size
def get(self, key):
"""Get the answer associated with I{key}. Returns None if
no answer is cached for the key.
@param key: the key
@type key: (dns.name.Name, int, int) tuple whose values are the
query name, rdtype, and rdclass.
@rtype: dns.resolver.Answer object or None
"""Get the answer associated with *key*.
Returns None if no answer is cached for the key.
*key*, a ``(dns.name.Name, int, int)`` tuple whose values are the
query name, rdtype, and rdclass respectively.
Returns a ``dns.resolver.Answer`` or ``None``.
"""
try:
self.lock.acquire()
node = self.data.get(key)
@ -471,12 +450,13 @@ class LRUCache(object):
def put(self, key, value):
"""Associate key and value in the cache.
@param key: the key
@type key: (dns.name.Name, int, int) tuple whose values are the
query name, rdtype, and rdclass.
@param value: The answer being cached
@type value: dns.resolver.Answer object
*key*, a ``(dns.name.Name, int, int)`` tuple whose values are the
query name, rdtype, and rdclass respectively.
*value*, a ``dns.resolver.Answer``, the answer.
"""
try:
self.lock.acquire()
node = self.data.get(key)
@ -496,12 +476,13 @@ class LRUCache(object):
def flush(self, key=None):
"""Flush the cache.
If I{key} is specified, only that item is flushed. Otherwise
If *key* is not ``None``, only that item is flushed. Otherwise
the entire cache is flushed.
@param key: the key to flush
@type key: (dns.name.Name, int, int) tuple or None
*key*, a ``(dns.name.Name, int, int)`` tuple whose values are the
query name, rdtype, and rdclass respectively.
"""
try:
self.lock.acquire()
if key is not None:
@ -522,62 +503,19 @@ class LRUCache(object):
class Resolver(object):
"""DNS stub resolver
@ivar domain: The domain of this host
@type domain: dns.name.Name object
@ivar nameservers: A list of nameservers to query. Each nameserver is
a string which contains the IP address of a nameserver.
@type nameservers: list of strings
@ivar search: The search list. If the query name is a relative name,
the resolver will construct an absolute query name by appending the search
names one by one to the query name.
@type search: list of dns.name.Name objects
@ivar port: The port to which to send queries. The default is 53.
@type port: int
@ivar timeout: The number of seconds to wait for a response from a
server, before timing out.
@type timeout: float
@ivar lifetime: The total number of seconds to spend trying to get an
answer to the question. If the lifetime expires, a Timeout exception
will occur.
@type lifetime: float
@ivar keyring: The TSIG keyring to use. The default is None.
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: dns.name.Name object
@ivar keyalgorithm: The TSIG key algorithm to use. The default is
dns.tsig.default_algorithm.
@type keyalgorithm: string
@ivar edns: The EDNS level to use. The default is -1, no Edns.
@type edns: int
@ivar ednsflags: The EDNS flags
@type ednsflags: int
@ivar payload: The EDNS payload size. The default is 0.
@type payload: int
@ivar flags: The message flags to use. The default is None (i.e. not
overwritten)
@type flags: int
@ivar cache: The cache to use. The default is None.
@type cache: dns.resolver.Cache object
@ivar retry_servfail: should we retry a nameserver if it says SERVFAIL?
The default is 'false'.
@type retry_servfail: bool
"""
"""DNS stub resolver."""
def __init__(self, filename='/etc/resolv.conf', configure=True):
"""Initialize a resolver instance.
"""*filename*, a ``text`` or file object, specifying a file
in standard /etc/resolv.conf format. This parameter is meaningful
only when *configure* is true and the platform is POSIX.
@param filename: The filename of a configuration file in
standard /etc/resolv.conf format. This parameter is meaningful
only when I{configure} is true and the platform is POSIX.
@type filename: string or file object
@param configure: If True (the default), the resolver instance
is configured in the normal fashion for the operating system
the resolver is running on. (I.e. a /etc/resolv.conf file on
POSIX systems and from the registry on Windows systems.)
@type configure: bool"""
*configure*, a ``bool``. If True (the default), the resolver
instance is configured in the normal fashion for the operating
system the resolver is running on. (I.e. by reading a
/etc/resolv.conf file on POSIX systems and from the registry
on Windows systems.)
"""
self.domain = None
self.nameservers = None
@ -606,6 +544,7 @@ class Resolver(object):
def reset(self):
"""Reset all resolver configuration to the defaults."""
self.domain = \
dns.name.Name(dns.name.from_text(socket.gethostname())[1:])
if len(self.domain) == 0:
@ -628,9 +567,10 @@ class Resolver(object):
self.rotate = False
def read_resolv_conf(self, f):
"""Process f as a file in the /etc/resolv.conf format. If f is
a string, it is used as the name of the file to open; otherwise it
"""Process *f* as a file in the /etc/resolv.conf format. If f is
a ``text``, it is used as the name of the file to open; otherwise it
is treated as the file itself."""
if isinstance(f, string_types):
try:
f = open(f, 'r')
@ -684,7 +624,6 @@ class Resolver(object):
return split_char
def _config_win32_nameservers(self, nameservers):
"""Configure a NameServer registry entry."""
# we call str() on nameservers to convert it from unicode to ascii
nameservers = str(nameservers)
split_char = self._determine_split_char(nameservers)
@ -694,12 +633,10 @@ class Resolver(object):
self.nameservers.append(ns)
def _config_win32_domain(self, domain):
"""Configure a Domain registry entry."""
# we call str() on domain to convert it from unicode to ascii
self.domain = dns.name.from_text(str(domain))
def _config_win32_search(self, search):
"""Configure a Search registry entry."""
# we call str() on search to convert it from unicode to ascii
search = str(search)
split_char = self._determine_split_char(search)
@ -708,14 +645,14 @@ class Resolver(object):
if s not in self.search:
self.search.append(dns.name.from_text(s))
def _config_win32_fromkey(self, key):
"""Extract DNS info from a registry key."""
def _config_win32_fromkey(self, key, always_try_domain):
try:
servers, rtype = _winreg.QueryValueEx(key, 'NameServer')
except WindowsError: # pylint: disable=undefined-variable
servers = None
if servers:
self._config_win32_nameservers(servers)
if servers or always_try_domain:
try:
dom, rtype = _winreg.QueryValueEx(key, 'Domain')
if dom:
@ -744,6 +681,7 @@ class Resolver(object):
def read_registry(self):
"""Extract resolver configuration from the Windows registry."""
lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
want_scan = False
try:
@ -759,7 +697,7 @@ class Resolver(object):
r'SYSTEM\CurrentControlSet'
r'\Services\VxD\MSTCP')
try:
self._config_win32_fromkey(tcp_params)
self._config_win32_fromkey(tcp_params, True)
finally:
tcp_params.Close()
if want_scan:
@ -777,7 +715,7 @@ class Resolver(object):
if not self._win32_is_nic_enabled(lm, guid, key):
continue
try:
self._config_win32_fromkey(key)
self._config_win32_fromkey(key, False)
finally:
key.Close()
except EnvironmentError:
@ -862,36 +800,43 @@ class Resolver(object):
tcp=False, source=None, raise_on_no_answer=True, source_port=0):
"""Query nameservers to find the answer to the question.
The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects
The *qname*, *rdtype*, and *rdclass* parameters may be objects
of the appropriate type, or strings that can be converted into objects
of the appropriate type. E.g. For I{rdtype} the integer 2 and the
the string 'NS' both mean to query for records with DNS rdata type NS.
of the appropriate type.
@param qname: the query name
@type qname: dns.name.Name object or string
@param rdtype: the query type
@type rdtype: int or string
@param rdclass: the query class
@type rdclass: int or string
@param tcp: use TCP to make the query (default is False).
@type tcp: bool
@param source: bind to this IP address (defaults to machine default
IP).
@type source: IP address in dotted quad notation
@param raise_on_no_answer: raise NoAnswer if there's no answer
(defaults is True).
@type raise_on_no_answer: bool
@param source_port: The port from which to send the message.
The default is 0.
@type source_port: int
@rtype: dns.resolver.Answer instance
@raises Timeout: no answers could be found in the specified lifetime
@raises NXDOMAIN: the query name does not exist
@raises YXDOMAIN: the query name is too long after DNAME substitution
@raises NoAnswer: the response did not contain an answer and
raise_on_no_answer is True.
@raises NoNameservers: no non-broken nameservers are available to
answer the question."""
*qname*, a ``dns.name.Name`` or ``text``, the query name.
*rdtype*, an ``int`` or ``text``, the query type.
*rdclass*, an ``int`` or ``text``, the query class.
*tcp*, a ``bool``. If ``True``, use TCP to make the query.
*source*, a ``text`` or ``None``. If not ``None``, bind to this IP
address when making queries.
*raise_on_no_answer*, a ``bool``. If ``True``, raise
``dns.resolver.NoAnswer`` if there's no answer to the question.
*source_port*, an ``int``, the port from which to send the message.
Raises ``dns.exception.Timeout`` if no answers could be found
in the specified lifetime.
Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist.
Raises ``dns.resolver.YXDOMAIN`` if the query name is too long after
DNAME substitution.
Raises ``dns.resolver.NoAnswer`` if *raise_on_no_answer* is
``True`` and the query name exists but has no RRset of the
desired type and class.
Raises ``dns.resolver.NoNameservers`` if no non-broken
nameservers are available to answer the question.
Returns a ``dns.resolver.Answer`` instance.
"""
if isinstance(qname, string_types):
qname = dns.name.from_text(qname, None)
@ -1059,17 +1004,22 @@ class Resolver(object):
algorithm=dns.tsig.default_algorithm):
"""Add a TSIG signature to the query.
@param keyring: The TSIG keyring to use; defaults to None.
@type keyring: dict
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key.
@param algorithm: The TSIG key algorithm to use. The default
is dns.tsig.default_algorithm.
@type algorithm: string"""
See the documentation of the Message class for a complete
description of the keyring dictionary.
*keyring*, a ``dict``, the TSIG keyring to use. If a
*keyring* is specified but a *keyname* is not, then the key
used will be the first key in the *keyring*. Note that the
order of keys in a dictionary is not defined, so applications
should supply a keyname when a keyring is used, unless they
know the keyring contains only one key.
*keyname*, a ``dns.name.Name`` or ``None``, the name of the TSIG key
to use; defaults to ``None``. The key must be defined in the keyring.
*algorithm*, a ``dns.name.Name``, the TSIG algorithm to use.
"""
self.keyring = keyring
if keyname is None:
self.keyname = list(self.keyring.keys())[0]
@ -1078,14 +1028,19 @@ class Resolver(object):
self.keyalgorithm = algorithm
def use_edns(self, edns, ednsflags, payload):
"""Configure Edns.
"""Configure EDNS behavior.
@param edns: The EDNS level to use. The default is -1, no Edns.
@type edns: int
@param ednsflags: The EDNS flags
@type ednsflags: int
@param payload: The EDNS payload size. The default is 0.
@type payload: int"""
*edns*, an ``int``, is the EDNS level to use. Specifying
``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case
the other parameters are ignored. Specifying ``True`` is
equivalent to specifying 0, i.e. "use EDNS0".
*ednsflags*, an ``int``, the EDNS flag values.
*payload*, an ``int``, is the EDNS sender's payload field, which is the
maximum size of UDP datagram the sender can handle. I.e. how big
a response to this message can be.
"""
if edns is None:
edns = -1
@ -1094,12 +1049,15 @@ class Resolver(object):
self.payload = payload
def set_flags(self, flags):
"""Overrides the default flags with your own
"""Overrides the default flags with your own.
*flags*, an ``int``, the message flags to use.
"""
@param flags: The flags to overwrite the default with
@type flags: int"""
self.flags = flags
#: The default resolver.
default_resolver = None
@ -1113,8 +1071,10 @@ def get_default_resolver():
def reset_default_resolver():
"""Re-initialize default resolver.
resolv.conf will be re-read immediatelly.
Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX
systems) will be re-read immediately.
"""
global default_resolver
default_resolver = Resolver()
@ -1126,8 +1086,11 @@ def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
This is a convenience function that uses the default resolver
object to make the query.
@see: L{dns.resolver.Resolver.query} for more information on the
parameters."""
See ``dns.resolver.Resolver.query`` for more information on the
parameters.
"""
return get_default_resolver().query(qname, rdtype, rdclass, tcp, source,
raise_on_no_answer, source_port)
@ -1135,15 +1098,21 @@ def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
"""Find the name of the zone which contains the specified name.
@param name: the query name
@type name: absolute dns.name.Name object or string
@param rdclass: The query class
@type rdclass: int
@param tcp: use TCP to make the query (default is False).
@type tcp: bool
@param resolver: the resolver to use
@type resolver: dns.resolver.Resolver object or None
@rtype: dns.name.Name"""
*name*, an absolute ``dns.name.Name`` or ``text``, the query name.
*rdclass*, an ``int``, the query class.
*tcp*, a ``bool``. If ``True``, use TCP to make the query.
*resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use.
If ``None``, the default resolver is used.
Raises ``dns.resolver.NoRootSOA`` if there is no SOA RR at the DNS
root. (This is only likely to happen if you're using non-default
root servers in your network and they are misconfigured.)
Returns a ``dns.name.Name``.
"""
if isinstance(name, string_types):
name = dns.name.from_text(name, dns.name.root)
@ -1379,9 +1348,9 @@ def override_system_resolver(resolver=None):
The resolver to use may be specified; if it's not, the default
resolver will be used.
@param resolver: the resolver to use
@type resolver: dns.resolver.Resolver object or None
resolver, a ``dns.resolver.Resolver`` or ``None``, the resolver to use.
"""
if resolver is None:
resolver = get_default_resolver()
global _resolver
@ -1395,8 +1364,8 @@ def override_system_resolver(resolver=None):
def restore_system_resolver():
"""Undo the effects of override_system_resolver().
"""
"""Undo the effects of prior override_system_resolver()."""
global _resolver
_resolver = None
socket.getaddrinfo = _original_getaddrinfo

View File

@ -1,4 +1,4 @@
# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2006-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,13 +13,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Reverse Map Names.
@var ipv4_reverse_domain: The DNS IPv4 reverse-map domain, in-addr.arpa.
@type ipv4_reverse_domain: dns.name.Name object
@var ipv6_reverse_domain: The DNS IPv6 reverse-map domain, ip6.arpa.
@type ipv6_reverse_domain: dns.name.Name object
"""
"""DNS Reverse Map Names."""
import binascii
import sys
@ -35,11 +29,15 @@ ipv6_reverse_domain = dns.name.from_text('ip6.arpa.')
def from_address(text):
"""Convert an IPv4 or IPv6 address in textual form into a Name object whose
value is the reverse-map domain name of the address.
@param text: an IPv4 or IPv6 address in textual form (e.g. '127.0.0.1',
'::1')
@type text: str
@rtype: dns.name.Name object
*text*, a ``text``, is an IPv4 or IPv6 address in textual form
(e.g. '127.0.0.1', '::1')
Raises ``dns.exception.SyntaxError`` if the address is badly formed.
Returns a ``dns.name.Name``.
"""
try:
v6 = dns.ipv6.inet_aton(text)
if dns.ipv6.is_mapped(v6):
@ -61,10 +59,16 @@ def from_address(text):
def to_address(name):
"""Convert a reverse map domain name into textual address form.
@param name: an IPv4 or IPv6 address in reverse-map form.
@type name: dns.name.Name object
@rtype: str
*name*, a ``dns.name.Name``, an IPv4 or IPv6 address in reverse-map name
form.
Raises ``dns.exception.SyntaxError`` if the name does not have a
reverse-map form.
Returns a ``text``.
"""
if name.is_subdomain(ipv4_reverse_domain):
name = name.relativize(ipv4_reverse_domain)
labels = list(name.labels)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -67,10 +67,6 @@ class RRset(dns.rdataset.Rdataset):
return self.to_text()
def __eq__(self, other):
"""Two RRsets are equal if they have the same name and the same
rdataset
@rtype: bool"""
if not isinstance(other, RRset):
return False
if self.name != other.name:
@ -78,8 +74,9 @@ class RRset(dns.rdataset.Rdataset):
return super(RRset, self).__eq__(other)
def match(self, name, rdclass, rdtype, covers, deleting=None):
"""Returns True if this rrset matches the specified class, type,
covers, and deletion state."""
"""Returns ``True`` if this rrset matches the specified class, type,
covers, and deletion state.
"""
if not super(RRset, self).match(rdclass, rdtype, covers):
return False
@ -90,23 +87,31 @@ class RRset(dns.rdataset.Rdataset):
def to_text(self, origin=None, relativize=True, **kw):
"""Convert the RRset into DNS master file format.
@see: L{dns.name.Name.choose_relativity} for more information
on how I{origin} and I{relativize} determine the way names
See ``dns.name.Name.choose_relativity`` for more information
on how *origin* and *relativize* determine the way names
are emitted.
Any additional keyword arguments are passed on to the rdata
to_text() method.
``to_text()`` method.
@param origin: The origin for relative names, or None.
@type origin: dns.name.Name object
@param relativize: True if names should names be relativized
@type relativize: bool"""
*origin*, a ``dns.name.Name`` or ``None``, the origin for relative
names.
*relativize*, a ``bool``. If ``True``, names will be relativized
to *origin*.
"""
return super(RRset, self).to_text(self.name, origin, relativize,
self.deleting, **kw)
def to_wire(self, file, compress=None, origin=None, **kw):
"""Convert the RRset to wire format."""
"""Convert the RRset to wire format.
All keyword arguments are passed to ``dns.rdataset.to_wire()``; see
that function for details.
Returns an ``int``, the number of records emitted.
"""
return super(RRset, self).to_wire(self.name, file, compress, origin,
self.deleting, **kw)
@ -114,7 +119,7 @@ class RRset(dns.rdataset.Rdataset):
def to_rdataset(self):
"""Convert an RRset into an Rdataset.
@rtype: dns.rdataset.Rdataset object
Returns a ``dns.rdataset.Rdataset``.
"""
return dns.rdataset.from_rdata_list(self.ttl, list(self))
@ -124,7 +129,7 @@ def from_text_list(name, ttl, rdclass, rdtype, text_rdatas,
"""Create an RRset with the specified name, TTL, class, and type, and with
the specified list of rdatas in text format.
@rtype: dns.rrset.RRset object
Returns a ``dns.rrset.RRset`` object.
"""
if isinstance(name, string_types):
@ -145,7 +150,7 @@ def from_text(name, ttl, rdclass, rdtype, *text_rdatas):
"""Create an RRset with the specified name, TTL, class, and type and with
the specified rdatas in text format.
@rtype: dns.rrset.RRset object
Returns a ``dns.rrset.RRset`` object.
"""
return from_text_list(name, ttl, rdclass, rdtype, text_rdatas)
@ -155,7 +160,7 @@ def from_rdata_list(name, ttl, rdatas, idna_codec=None):
"""Create an RRset with the specified name and TTL, and with
the specified list of rdata objects.
@rtype: dns.rrset.RRset object
Returns a ``dns.rrset.RRset`` object.
"""
if isinstance(name, string_types):
@ -176,7 +181,7 @@ def from_rdata(name, ttl, *rdatas):
"""Create an RRset with the specified name and TTL, and with
the specified rdata objects.
@rtype: dns.rrset.RRset object
Returns a ``dns.rrset.RRset`` object.
"""
return from_rdata_list(name, ttl, rdatas)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -13,27 +13,22 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""A simple Set class."""
class Set(object):
"""A simple set class.
Sets are not in Python until 2.3, and rdata are not immutable so
we cannot use sets.Set anyway. This class implements subset of
the 2.3 Set interface using a list as the container.
@ivar items: A list of the items which are in the set
@type items: list"""
This class was originally used to deal with sets being missing in
ancient versions of python, but dnspython will continue to use it
as these sets are based on lists and are thus indexable, and this
ability is widely used in dnspython applications.
"""
__slots__ = ['items']
def __init__(self, items=None):
"""Initialize the set.
@param items: the initial set of items
@type items: any iterable or None
*items*, an iterable or ``None``, the initial set of items.
"""
self.items = []
@ -45,16 +40,22 @@ class Set(object):
return "dns.simpleset.Set(%s)" % repr(self.items)
def add(self, item):
"""Add an item to the set."""
"""Add an item to the set.
"""
if item not in self.items:
self.items.append(item)
def remove(self, item):
"""Remove an item from the set."""
"""Remove an item from the set.
"""
self.items.remove(item)
def discard(self, item):
"""Remove an item from the set if present."""
"""Remove an item from the set if present.
"""
try:
self.items.remove(item)
except ValueError:
@ -79,19 +80,22 @@ class Set(object):
return obj
def __copy__(self):
"""Make a (shallow) copy of the set."""
"""Make a (shallow) copy of the set.
"""
return self._clone()
def copy(self):
"""Make a (shallow) copy of the set."""
"""Make a (shallow) copy of the set.
"""
return self._clone()
def union_update(self, other):
"""Update the set, adding any elements from other which are not
already in the set.
@param other: the collection of items with which to update the set
@type other: Set object
"""
if not isinstance(other, Set):
raise ValueError('other must be a Set instance')
if self is other:
@ -102,9 +106,8 @@ class Set(object):
def intersection_update(self, other):
"""Update the set, removing any elements from other which are not
in both sets.
@param other: the collection of items with which to update the set
@type other: Set object
"""
if not isinstance(other, Set):
raise ValueError('other must be a Set instance')
if self is other:
@ -118,9 +121,8 @@ class Set(object):
def difference_update(self, other):
"""Update the set, removing any elements from other which are in
the set.
@param other: the collection of items with which to update the set
@type other: Set object
"""
if not isinstance(other, Set):
raise ValueError('other must be a Set instance')
if self is other:
@ -130,11 +132,9 @@ class Set(object):
self.discard(item)
def union(self, other):
"""Return a new set which is the union of I{self} and I{other}.
"""Return a new set which is the union of ``self`` and ``other``.
@param other: the other set
@type other: Set object
@rtype: the same type as I{self}
Returns the same Set type as this set.
"""
obj = self._clone()
@ -142,11 +142,10 @@ class Set(object):
return obj
def intersection(self, other):
"""Return a new set which is the intersection of I{self} and I{other}.
"""Return a new set which is the intersection of ``self`` and
``other``.
@param other: the other set
@type other: Set object
@rtype: the same type as I{self}
Returns the same Set type as this set.
"""
obj = self._clone()
@ -154,12 +153,10 @@ class Set(object):
return obj
def difference(self, other):
"""Return a new set which I{self} - I{other}, i.e. the items
in I{self} which are not also in I{other}.
"""Return a new set which ``self`` - ``other``, i.e. the items
in ``self`` which are not also in ``other``.
@param other: the other set
@type other: Set object
@rtype: the same type as I{self}
Returns the same Set type as this set.
"""
obj = self._clone()
@ -197,8 +194,11 @@ class Set(object):
def update(self, other):
"""Update the set, adding any elements from other which are not
already in the set.
@param other: the collection of items with which to update the set
@type other: any iterable type"""
*other*, the collection of items with which to update the set, which
may be any iterable type.
"""
for item in other:
self.add(item)
@ -233,9 +233,9 @@ class Set(object):
del self.items[i]
def issubset(self, other):
"""Is I{self} a subset of I{other}?
"""Is this set a subset of *other*?
@rtype: bool
Returns a ``bool``.
"""
if not isinstance(other, Set):
@ -246,9 +246,9 @@ class Set(object):
return True
def issuperset(self, other):
"""Is I{self} a superset of I{other}?
"""Is this set a superset of *other*?
@rtype: bool
Returns a ``bool``.
"""
if not isinstance(other, Set):

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -44,32 +44,20 @@ DELIMITER = 6
class UngetBufferFull(dns.exception.DNSException):
"""An attempt was made to unget a token when the unget buffer was full."""
class Token(object):
"""A DNS master file format token.
@ivar ttype: The token type
@type ttype: int
@ivar value: The token value
@type value: string
@ivar has_escape: Does the token value contain escapes?
@type has_escape: bool
ttype: The token type
value: The token value
has_escape: Does the token value contain escapes?
"""
def __init__(self, ttype, value='', has_escape=False):
"""Initialize a token instance.
"""Initialize a token instance."""
@param ttype: The token type
@type ttype: int
@param value: The token value
@type value: string
@param has_escape: Does the token value contain escapes?
@type has_escape: bool
"""
self.ttype = ttype
self.value = value
self.has_escape = has_escape
@ -160,46 +148,43 @@ class Token(object):
class Tokenizer(object):
"""A DNS master file format tokenizer.
A token is a (type, value) tuple, where I{type} is an int, and
I{value} is a string. The valid types are EOF, EOL, WHITESPACE,
IDENTIFIER, QUOTED_STRING, COMMENT, and DELIMITER.
A token object is basically a (type, value) tuple. The valid
types are EOF, EOL, WHITESPACE, IDENTIFIER, QUOTED_STRING,
COMMENT, and DELIMITER.
@ivar file: The file to tokenize
@type file: file
@ivar ungotten_char: The most recently ungotten character, or None.
@type ungotten_char: string
@ivar ungotten_token: The most recently ungotten token, or None.
@type ungotten_token: (int, string) token tuple
@ivar multiline: The current multiline level. This value is increased
file: The file to tokenize
ungotten_char: The most recently ungotten character, or None.
ungotten_token: The most recently ungotten token, or None.
multiline: The current multiline level. This value is increased
by one every time a '(' delimiter is read, and decreased by one every time
a ')' delimiter is read.
@type multiline: int
@ivar quoting: This variable is true if the tokenizer is currently
quoting: This variable is true if the tokenizer is currently
reading a quoted string.
@type quoting: bool
@ivar eof: This variable is true if the tokenizer has encountered EOF.
@type eof: bool
@ivar delimiters: The current delimiter dictionary.
@type delimiters: dict
@ivar line_number: The current line number
@type line_number: int
@ivar filename: A filename that will be returned by the L{where} method.
@type filename: string
eof: This variable is true if the tokenizer has encountered EOF.
delimiters: The current delimiter dictionary.
line_number: The current line number
filename: A filename that will be returned by the where() method.
"""
def __init__(self, f=sys.stdin, filename=None):
"""Initialize a tokenizer instance.
@param f: The file to tokenize. The default is sys.stdin.
f: The file to tokenize. The default is sys.stdin.
This parameter may also be a string, in which case the tokenizer
will take its input from the contents of the string.
@type f: file or string
@param filename: the name of the filename that the L{where} method
filename: the name of the filename that the where() method
will return.
@type filename: string
"""
if isinstance(f, text_type):
@ -228,7 +213,6 @@ class Tokenizer(object):
def _get_char(self):
"""Read a character from input.
@rtype: string
"""
if self.ungotten_char is None:
@ -248,7 +232,7 @@ class Tokenizer(object):
def where(self):
"""Return the current location in the input.
@rtype: (string, int) tuple. The first item is the filename of
Returns a (string, int) tuple. The first item is the filename of
the input, the second is the current line number.
"""
@ -261,9 +245,8 @@ class Tokenizer(object):
an error to try to unget a character when the unget buffer is not
empty.
@param c: the character to unget
@type c: string
@raises UngetBufferFull: there is already an ungotten char
c: the character to unget
raises UngetBufferFull: there is already an ungotten char
"""
if self.ungotten_char is not None:
@ -278,7 +261,7 @@ class Tokenizer(object):
If the tokenizer is in multiline mode, then newlines are whitespace.
@rtype: int
Returns the number of characters skipped.
"""
skipped = 0
@ -293,15 +276,17 @@ class Tokenizer(object):
def get(self, want_leading=False, want_comment=False):
"""Get the next token.
@param want_leading: If True, return a WHITESPACE token if the
want_leading: If True, return a WHITESPACE token if the
first character read is whitespace. The default is False.
@type want_leading: bool
@param want_comment: If True, return a COMMENT token if the
want_comment: If True, return a COMMENT token if the
first token read is a comment. The default is False.
@type want_comment: bool
@rtype: Token object
@raises dns.exception.UnexpectedEnd: input ended prematurely
@raises dns.exception.SyntaxError: input was badly formed
Raises dns.exception.UnexpectedEnd: input ended prematurely
Raises dns.exception.SyntaxError: input was badly formed
Returns a Token.
"""
if self.ungotten_token is not None:
@ -420,9 +405,9 @@ class Tokenizer(object):
an error to try to unget a token when the unget buffer is not
empty.
@param token: the token to unget
@type token: Token object
@raises UngetBufferFull: there is already an ungotten token
token: the token to unget
Raises UngetBufferFull: there is already an ungotten token
"""
if self.ungotten_token is not None:
@ -431,7 +416,8 @@ class Tokenizer(object):
def next(self):
"""Return the next item in an iteration.
@rtype: (int, string)
Returns a Token.
"""
token = self.get()
@ -449,8 +435,9 @@ class Tokenizer(object):
def get_int(self):
"""Read the next token and interpret it as an integer.
@raises dns.exception.SyntaxError:
@rtype: int
Raises dns.exception.SyntaxError if not an integer.
Returns an int.
"""
token = self.get().unescape()
@ -464,8 +451,9 @@ class Tokenizer(object):
"""Read the next token and interpret it as an 8-bit unsigned
integer.
@raises dns.exception.SyntaxError:
@rtype: int
Raises dns.exception.SyntaxError if not an 8-bit unsigned integer.
Returns an int.
"""
value = self.get_int()
@ -478,8 +466,9 @@ class Tokenizer(object):
"""Read the next token and interpret it as a 16-bit unsigned
integer.
@raises dns.exception.SyntaxError:
@rtype: int
Raises dns.exception.SyntaxError if not a 16-bit unsigned integer.
Returns an int.
"""
value = self.get_int()
@ -492,8 +481,9 @@ class Tokenizer(object):
"""Read the next token and interpret it as a 32-bit unsigned
integer.
@raises dns.exception.SyntaxError:
@rtype: int
Raises dns.exception.SyntaxError if not a 32-bit unsigned integer.
Returns an int.
"""
token = self.get().unescape()
@ -510,8 +500,9 @@ class Tokenizer(object):
def get_string(self, origin=None):
"""Read the next token and interpret it as a string.
@raises dns.exception.SyntaxError:
@rtype: string
Raises dns.exception.SyntaxError if not a string.
Returns a string.
"""
token = self.get().unescape()
@ -520,10 +511,11 @@ class Tokenizer(object):
return token.value
def get_identifier(self, origin=None):
"""Read the next token and raise an exception if it is not an identifier.
"""Read the next token, which should be an identifier.
@raises dns.exception.SyntaxError:
@rtype: string
Raises dns.exception.SyntaxError if not an identifier.
Returns a string.
"""
token = self.get().unescape()
@ -534,8 +526,10 @@ class Tokenizer(object):
def get_name(self, origin=None):
"""Read the next token and interpret it as a DNS name.
@raises dns.exception.SyntaxError:
@rtype: dns.name.Name object"""
Raises dns.exception.SyntaxError if not a name.
Returns a dns.name.Name.
"""
token = self.get()
if not token.is_identifier():
@ -546,8 +540,7 @@ class Tokenizer(object):
"""Read the next token and raise an exception if it isn't EOL or
EOF.
@raises dns.exception.SyntaxError:
@rtype: string
Returns a string.
"""
token = self.get()
@ -558,6 +551,14 @@ class Tokenizer(object):
return token.value
def get_ttl(self):
"""Read the next token and interpret it as a DNS TTL.
Raises dns.exception.SyntaxError or dns.ttl.BadTTL if not an
identifier or badly formed.
Returns an int.
"""
token = self.get().unescape()
if not token.is_identifier():
raise dns.exception.SyntaxError('expecting an identifier')

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -20,7 +20,6 @@ from ._compat import long
class BadTTL(dns.exception.SyntaxError):
"""DNS TTL value is not well-formed."""
@ -29,10 +28,11 @@ def from_text(text):
The BIND 8 units syntax for TTLs (e.g. '1w6d4h3m10s') is supported.
@param text: the textual TTL
@type text: string
@raises dns.ttl.BadTTL: the TTL is not well-formed
@rtype: int
*text*, a ``text``, the textual TTL.
Raises ``dns.ttl.BadTTL`` if the TTL is not well-formed.
Returns an ``int``.
"""
if text.isdigit():

View File

@ -32,26 +32,25 @@ class Update(dns.message.Message):
keyname=None, keyalgorithm=dns.tsig.default_algorithm):
"""Initialize a new DNS Update object.
@param zone: The zone which is being updated.
@type zone: A dns.name.Name or string
@param rdclass: The class of the zone; defaults to dns.rdataclass.IN.
@type rdclass: An int designating the class, or a string whose value
is the name of a class.
@param keyring: The TSIG keyring to use; defaults to None.
@type keyring: dict
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key.
@type keyname: dns.name.Name or string
@param keyalgorithm: The TSIG algorithm to use; defaults to
dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
in dns.tsig, and the currently implemented algorithms are
HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
HMAC_SHA512.
@type keyalgorithm: string
See the documentation of the Message class for a complete
description of the keyring dictionary.
*zone*, a ``dns.name.Name`` or ``text``, the zone which is being
updated.
*rdclass*, an ``int`` or ``text``, the class of the zone.
*keyring*, a ``dict``, the TSIG keyring to use. If a
*keyring* is specified but a *keyname* is not, then the key
used will be the first key in the *keyring*. Note that the
order of keys in a dictionary is not defined, so applications
should supply a keyname when a keyring is used, unless they
know the keyring contains only one key.
*keyname*, a ``dns.name.Name`` or ``None``, the name of the TSIG key
to use; defaults to ``None``. The key must be defined in the keyring.
*keyalgorithm*, a ``dns.name.Name``, the TSIG algorithm to use.
"""
super(Update, self).__init__()
self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE)
@ -77,8 +76,10 @@ class Update(dns.message.Message):
rrset.add(rd, ttl)
def _add(self, replace, section, name, *args):
"""Add records. The first argument is the replace mode. If
false, RRs are added to an existing RRset; if true, the RRset
"""Add records.
*replace* is the replacement mode. If ``False``,
RRs are added to an existing RRset; if ``True``, the RRset
is replaced with the specified contents. The second
argument is the section to add to. The third argument
is always a name. The other arguments can be:
@ -87,7 +88,8 @@ class Update(dns.message.Message):
- ttl, rdata...
- ttl, rdtype, string..."""
- ttl, rdtype, string...
"""
if isinstance(name, string_types):
name = dns.name.from_text(name, None)
@ -117,27 +119,34 @@ class Update(dns.message.Message):
self._add_rr(name, ttl, rd, section=section)
def add(self, name, *args):
"""Add records. The first argument is always a name. The other
"""Add records.
The first argument is always a name. The other
arguments can be:
- rdataset...
- ttl, rdata...
- ttl, rdtype, string..."""
- ttl, rdtype, string...
"""
self._add(False, self.authority, name, *args)
def delete(self, name, *args):
"""Delete records. The first argument is always a name. The other
"""Delete records.
The first argument is always a name. The other
arguments can be:
- I{nothing}
- *empty*
- rdataset...
- rdata...
- rdtype, [string...]"""
- rdtype, [string...]
"""
if isinstance(name, string_types):
name = dns.name.from_text(name, None)
@ -171,7 +180,9 @@ class Update(dns.message.Message):
self._add_rr(name, 0, rd, dns.rdataclass.NONE)
def replace(self, name, *args):
"""Replace records. The first argument is always a name. The other
"""Replace records.
The first argument is always a name. The other
arguments can be:
- rdataset...
@ -181,21 +192,25 @@ class Update(dns.message.Message):
- ttl, rdtype, string...
Note that if you want to replace the entire node, you should do
a delete of the name followed by one or more calls to add."""
a delete of the name followed by one or more calls to add.
"""
self._add(True, self.authority, name, *args)
def present(self, name, *args):
"""Require that an owner name (and optionally an rdata type,
or specific rdataset) exists as a prerequisite to the
execution of the update. The first argument is always a name.
execution of the update.
The first argument is always a name.
The other arguments can be:
- rdataset...
- rdata...
- rdtype, string..."""
- rdtype, string...
"""
if isinstance(name, string_types):
name = dns.name.from_text(name, None)
@ -243,7 +258,20 @@ class Update(dns.message.Message):
def to_wire(self, origin=None, max_size=65535):
"""Return a string containing the update in DNS compressed wire
format.
@rtype: string"""
*origin*, a ``dns.name.Name`` or ``None``, the origin to be
appended to any relative names. If *origin* is ``None``, then
the origin of the ``dns.update.Update`` message object is used
(i.e. the *zone* parameter passed when the Update object was
created).
*max_size*, an ``int``, the maximum size of the wire format
output; default is 0, which means "the message's request
payload, if nonzero, or 65535".
Returns a ``binary``.
"""
if origin is None:
origin = self.origin
return super(Update, self).to_wire(origin, max_size)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2003-2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -15,13 +15,19 @@
"""dnspython release version information."""
#: MAJOR
MAJOR = 1
MINOR = 15
#: MINOR
MINOR = 16
#: MICRO
MICRO = 0
#: RELEASELEVEL
RELEASELEVEL = 0x0f
#: SERIAL
SERIAL = 0
if RELEASELEVEL == 0x0f:
#: version
version = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
elif RELEASELEVEL == 0x00:
version = '%d.%d.%dx%d' % \
@ -30,5 +36,6 @@ else:
version = '%d.%d.%d%x%d' % \
(MAJOR, MINOR, MICRO, RELEASELEVEL, SERIAL)
#: hexversion
hexversion = MAJOR << 24 | MINOR << 16 | MICRO << 8 | RELEASELEVEL << 4 | \
SERIAL

View File

@ -1,4 +1,4 @@
# Copyright (C) 2011 Nominum, Inc.
# Copyright (C) 2011,2017 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
@ -40,7 +40,7 @@ _unspecified_bound = _SliceUnspecifiedBound()[1:]
class WireData(binary_type):
# WireData is a string with stricter slicing
# WireData is a binary type with stricter slicing
def __getitem__(self, key):
try: