support: upgrade bundled dnspython to 1.16.0 (22e9de1d7957e)
https://github.com/eventlet/eventlet/issues/427
This commit is contained in:
parent
ce72c4378b
commit
2d4fa74645
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'.')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
#
|
||||
|
|
|
@ -20,6 +20,7 @@ __all__ = [
|
|||
'AAAA',
|
||||
'APL',
|
||||
'DHCID',
|
||||
'IPSECKEY',
|
||||
'KX',
|
||||
'NAPTR',
|
||||
'NSAP',
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue