| Index: third_party/twisted_8_1/twisted/names/dns.py
|
| diff --git a/third_party/twisted_8_1/twisted/names/dns.py b/third_party/twisted_8_1/twisted/names/dns.py
|
| deleted file mode 100644
|
| index eaebb48ef3aa0541c80b0850561571fbc4d966b9..0000000000000000000000000000000000000000
|
| --- a/third_party/twisted_8_1/twisted/names/dns.py
|
| +++ /dev/null
|
| @@ -1,1621 +0,0 @@
|
| -# -*- test-case-name: twisted.names.test.test_dns -*-
|
| -# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
|
| -# See LICENSE for details.
|
| -
|
| -
|
| -"""
|
| -DNS protocol implementation.
|
| -
|
| -Future Plans:
|
| - - Get rid of some toplevels, maybe.
|
| - - Put in a better lookupRecordType implementation.
|
| -
|
| -@author: U{Moshe Zadka<mailto:moshez@twistedmatrix.com>},
|
| - U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
|
| -"""
|
| -
|
| -# System imports
|
| -import warnings
|
| -
|
| -import struct, random, types, socket
|
| -
|
| -try:
|
| - import cStringIO as StringIO
|
| -except ImportError:
|
| - import StringIO
|
| -
|
| -AF_INET6 = socket.AF_INET6
|
| -
|
| -from zope.interface import implements, Interface
|
| -
|
| -
|
| -# Twisted imports
|
| -from twisted.internet import protocol, defer
|
| -from twisted.internet.error import CannotListenError
|
| -from twisted.python import log, failure
|
| -from twisted.python import util as tputil
|
| -from twisted.python import randbytes
|
| -
|
| -
|
| -def randomSource():
|
| - """
|
| - Wrapper around L{randbytes.secureRandom} to return 2 random chars.
|
| - """
|
| - return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
|
| -
|
| -
|
| -PORT = 53
|
| -
|
| -(A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
|
| - RP, AFSDB) = range(1, 19)
|
| -AAAA = 28
|
| -SRV = 33
|
| -A6 = 38
|
| -DNAME = 39
|
| -
|
| -QUERY_TYPES = {
|
| - A: 'A',
|
| - NS: 'NS',
|
| - MD: 'MD',
|
| - MF: 'MF',
|
| - CNAME: 'CNAME',
|
| - SOA: 'SOA',
|
| - MB: 'MB',
|
| - MG: 'MG',
|
| - MR: 'MR',
|
| - NULL: 'NULL',
|
| - WKS: 'WKS',
|
| - PTR: 'PTR',
|
| - HINFO: 'HINFO',
|
| - MINFO: 'MINFO',
|
| - MX: 'MX',
|
| - TXT: 'TXT',
|
| - RP: 'RP',
|
| - AFSDB: 'AFSDB',
|
| -
|
| - # 19 through 27? Eh, I'll get to 'em.
|
| -
|
| - AAAA: 'AAAA',
|
| - SRV: 'SRV',
|
| -
|
| - A6: 'A6',
|
| - DNAME: 'DNAME'
|
| -}
|
| -
|
| -IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
|
| -
|
| -# "Extended" queries (Hey, half of these are deprecated, good job)
|
| -EXT_QUERIES = {
|
| - IXFR: 'IXFR',
|
| - AXFR: 'AXFR',
|
| - MAILB: 'MAILB',
|
| - MAILA: 'MAILA',
|
| - ALL_RECORDS: 'ALL_RECORDS'
|
| -}
|
| -
|
| -REV_TYPES = dict([
|
| - (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
|
| -])
|
| -
|
| -IN, CS, CH, HS = range(1, 5)
|
| -ANY = 255
|
| -
|
| -QUERY_CLASSES = {
|
| - IN: 'IN',
|
| - CS: 'CS',
|
| - CH: 'CH',
|
| - HS: 'HS',
|
| - ANY: 'ANY'
|
| -}
|
| -REV_CLASSES = dict([
|
| - (v, k) for (k, v) in QUERY_CLASSES.items()
|
| -])
|
| -
|
| -
|
| -# Opcodes
|
| -OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
|
| -OP_NOTIFY = 4 # RFC 1996
|
| -OP_UPDATE = 5 # RFC 2136
|
| -
|
| -
|
| -# Response Codes
|
| -OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
|
| -
|
| -class IRecord(Interface):
|
| - """
|
| - An single entry in a zone of authority.
|
| -
|
| - @cvar TYPE: An indicator of what kind of record this is.
|
| - """
|
| -
|
| -
|
| -# Backwards compatibility aliases - these should be deprecated or something I
|
| -# suppose. -exarkun
|
| -from twisted.names.error import DomainError, AuthoritativeDomainError
|
| -from twisted.names.error import DNSQueryTimeoutError
|
| -
|
| -
|
| -def str2time(s):
|
| - suffixes = (
|
| - ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
|
| - ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
|
| - )
|
| - if isinstance(s, types.StringType):
|
| - s = s.upper().strip()
|
| - for (suff, mult) in suffixes:
|
| - if s.endswith(suff):
|
| - return int(float(s[:-1]) * mult)
|
| - try:
|
| - s = int(s)
|
| - except ValueError:
|
| - raise ValueError, "Invalid time interval specifier: " + s
|
| - return s
|
| -
|
| -
|
| -def readPrecisely(file, l):
|
| - buff = file.read(l)
|
| - if len(buff) < l:
|
| - raise EOFError
|
| - return buff
|
| -
|
| -
|
| -class IEncodable(Interface):
|
| - """
|
| - Interface for something which can be encoded to and decoded
|
| - from a file object.
|
| - """
|
| - def encode(strio, compDict = None):
|
| - """
|
| - Write a representation of this object to the given
|
| - file object.
|
| -
|
| - @type strio: File-like object
|
| - @param strio: The stream to which to write bytes
|
| -
|
| - @type compDict: C{dict} or C{None}
|
| - @param compDict: A dictionary of backreference addresses that have
|
| - have already been written to this stream and that may be used for
|
| - compression.
|
| - """
|
| -
|
| - def decode(strio, length = None):
|
| - """
|
| - Reconstruct an object from data read from the given
|
| - file object.
|
| -
|
| - @type strio: File-like object
|
| - @param strio: The stream from which bytes may be read
|
| -
|
| - @type length: C{int} or C{None}
|
| - @param length: The number of bytes in this RDATA field. Most
|
| - implementations can ignore this value. Only in the case of
|
| - records similar to TXT where the total length is in no way
|
| - encoded in the data is it necessary.
|
| - """
|
| -
|
| -
|
| -class Name:
|
| - implements(IEncodable)
|
| -
|
| - def __init__(self, name=''):
|
| - assert isinstance(name, types.StringTypes), "%r is not a string" % (name,)
|
| - self.name = name
|
| -
|
| - def encode(self, strio, compDict=None):
|
| - """
|
| - Encode this Name into the appropriate byte format.
|
| -
|
| - @type strio: file
|
| - @param strio: The byte representation of this Name will be written to
|
| - this file.
|
| -
|
| - @type compDict: dict
|
| - @param compDict: dictionary of Names that have already been encoded
|
| - and whose addresses may be backreferenced by this Name (for the purpose
|
| - of reducing the message size).
|
| - """
|
| - name = self.name
|
| - while name:
|
| - if compDict is not None:
|
| - if name in compDict:
|
| - strio.write(
|
| - struct.pack("!H", 0xc000 | compDict[name]))
|
| - return
|
| - else:
|
| - compDict[name] = strio.tell() + Message.headerSize
|
| - ind = name.find('.')
|
| - if ind > 0:
|
| - label, name = name[:ind], name[ind + 1:]
|
| - else:
|
| - label, name = name, ''
|
| - ind = len(label)
|
| - strio.write(chr(ind))
|
| - strio.write(label)
|
| - strio.write(chr(0))
|
| -
|
| -
|
| - def decode(self, strio, length=None):
|
| - """
|
| - Decode a byte string into this Name.
|
| -
|
| - @type strio: file
|
| - @param strio: Bytes will be read from this file until the full Name
|
| - is decoded.
|
| -
|
| - @raise EOFError: Raised when there are not enough bytes available
|
| - from C{strio}.
|
| - """
|
| - self.name = ''
|
| - off = 0
|
| - while 1:
|
| - l = ord(readPrecisely(strio, 1))
|
| - if l == 0:
|
| - if off > 0:
|
| - strio.seek(off)
|
| - return
|
| - if (l >> 6) == 3:
|
| - new_off = ((l&63) << 8
|
| - | ord(readPrecisely(strio, 1)))
|
| - if off == 0:
|
| - off = strio.tell()
|
| - strio.seek(new_off)
|
| - continue
|
| - label = readPrecisely(strio, l)
|
| - if self.name == '':
|
| - self.name = label
|
| - else:
|
| - self.name = self.name + '.' + label
|
| -
|
| - def __eq__(self, other):
|
| - if isinstance(other, Name):
|
| - return str(self) == str(other)
|
| - return 0
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash(str(self))
|
| -
|
| -
|
| - def __str__(self):
|
| - return self.name
|
| -
|
| -class Query:
|
| - """
|
| - Represent a single DNS query.
|
| -
|
| - @ivar name: The name about which this query is requesting information.
|
| - @ivar type: The query type.
|
| - @ivar cls: The query class.
|
| - """
|
| -
|
| - implements(IEncodable)
|
| -
|
| - name = None
|
| - type = None
|
| - cls = None
|
| -
|
| - def __init__(self, name='', type=A, cls=IN):
|
| - """
|
| - @type name: C{str}
|
| - @param name: The name about which to request information.
|
| -
|
| - @type type: C{int}
|
| - @param type: The query type.
|
| -
|
| - @type cls: C{int}
|
| - @param cls: The query class.
|
| - """
|
| - self.name = Name(name)
|
| - self.type = type
|
| - self.cls = cls
|
| -
|
| -
|
| - def encode(self, strio, compDict=None):
|
| - self.name.encode(strio, compDict)
|
| - strio.write(struct.pack("!HH", self.type, self.cls))
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.name.decode(strio)
|
| - buff = readPrecisely(strio, 4)
|
| - self.type, self.cls = struct.unpack("!HH", buff)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((str(self.name).lower(), self.type, self.cls))
|
| -
|
| -
|
| - def __cmp__(self, other):
|
| - return isinstance(other, Query) and cmp(
|
| - (str(self.name).lower(), self.type, self.cls),
|
| - (str(other.name).lower(), other.type, other.cls)
|
| - ) or cmp(self.__class__, other.__class__)
|
| -
|
| -
|
| - def __str__(self):
|
| - t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
|
| - c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
|
| - return '<Query %s %s %s>' % (self.name, t, c)
|
| -
|
| -
|
| - def __repr__(self):
|
| - return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
|
| -
|
| -
|
| -class RRHeader:
|
| - """
|
| - A resource record header.
|
| -
|
| - @cvar fmt: C{str} specifying the byte format of an RR.
|
| -
|
| - @ivar name: The name about which this reply contains information.
|
| - @ivar type: The query type of the original request.
|
| - @ivar cls: The query class of the original request.
|
| - @ivar ttl: The time-to-live for this record.
|
| - @ivar payload: An object that implements the IEncodable interface
|
| - @ivar auth: Whether this header is authoritative or not.
|
| - """
|
| -
|
| - implements(IEncodable)
|
| -
|
| - fmt = "!HHIH"
|
| -
|
| - name = None
|
| - type = None
|
| - cls = None
|
| - ttl = None
|
| - payload = None
|
| - rdlength = None
|
| -
|
| - cachedResponse = None
|
| -
|
| - def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False):
|
| - """
|
| - @type name: C{str}
|
| - @param name: The name about which this reply contains information.
|
| -
|
| - @type type: C{int}
|
| - @param type: The query type.
|
| -
|
| - @type cls: C{int}
|
| - @param cls: The query class.
|
| -
|
| - @type ttl: C{int}
|
| - @param ttl: Time to live for this record.
|
| -
|
| - @type payload: An object implementing C{IEncodable}
|
| - @param payload: A Query Type specific data object.
|
| - """
|
| - assert (payload is None) or (payload.TYPE == type)
|
| -
|
| - self.name = Name(name)
|
| - self.type = type
|
| - self.cls = cls
|
| - self.ttl = ttl
|
| - self.payload = payload
|
| - self.auth = auth
|
| -
|
| -
|
| - def encode(self, strio, compDict=None):
|
| - self.name.encode(strio, compDict)
|
| - strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
|
| - if self.payload:
|
| - prefix = strio.tell()
|
| - self.payload.encode(strio, compDict)
|
| - aft = strio.tell()
|
| - strio.seek(prefix - 2, 0)
|
| - strio.write(struct.pack('!H', aft - prefix))
|
| - strio.seek(aft, 0)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.name.decode(strio)
|
| - l = struct.calcsize(self.fmt)
|
| - buff = readPrecisely(strio, l)
|
| - r = struct.unpack(self.fmt, buff)
|
| - self.type, self.cls, self.ttl, self.rdlength = r
|
| -
|
| -
|
| - def isAuthoritative(self):
|
| - return self.auth
|
| -
|
| -
|
| - def __str__(self):
|
| - t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
|
| - c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
|
| - return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
|
| -
|
| -
|
| - __repr__ = __str__
|
| -
|
| -
|
| -
|
| -class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
|
| - """
|
| - A Resource Record which consists of a single RFC 1035 domain-name.
|
| -
|
| - @type name: L{Name}
|
| - @ivar name: The name associated with this record.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| -
|
| - showAttributes = (('name', 'name', '%s'), 'ttl')
|
| - compareAttributes = ('name', 'ttl')
|
| -
|
| - TYPE = None
|
| - name = None
|
| -
|
| - def __init__(self, name='', ttl=None):
|
| - self.name = Name(name)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - self.name.encode(strio, compDict)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.name = Name()
|
| - self.name.decode(strio)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash(self.name)
|
| -
|
| -
|
| -# Kinds of RRs - oh my!
|
| -class Record_NS(SimpleRecord):
|
| - """
|
| - An authoritative nameserver.
|
| - """
|
| - TYPE = NS
|
| -
|
| -
|
| -
|
| -class Record_MD(SimpleRecord):
|
| - """
|
| - A mail destination.
|
| -
|
| - This record type is obsolete.
|
| -
|
| - @see: L{Record_MX}
|
| - """
|
| - TYPE = MD
|
| -
|
| -
|
| -
|
| -class Record_MF(SimpleRecord):
|
| - """
|
| - A mail forwarder.
|
| -
|
| - This record type is obsolete.
|
| -
|
| - @see: L{Record_MX}
|
| - """
|
| - TYPE = MF
|
| -
|
| -
|
| -
|
| -class Record_CNAME(SimpleRecord):
|
| - """
|
| - The canonical name for an alias.
|
| - """
|
| - TYPE = CNAME
|
| -
|
| -
|
| -
|
| -class Record_MB(SimpleRecord):
|
| - """
|
| - A mailbox domain name.
|
| -
|
| - This is an experimental record type.
|
| - """
|
| - TYPE = MB
|
| -
|
| -
|
| -
|
| -class Record_MG(SimpleRecord):
|
| - """
|
| - A mail group member.
|
| -
|
| - This is an experimental record type.
|
| - """
|
| - TYPE = MG
|
| -
|
| -
|
| -
|
| -class Record_MR(SimpleRecord):
|
| - """
|
| - A mail rename domain name.
|
| -
|
| - This is an experimental record type.
|
| - """
|
| - TYPE = MR
|
| -
|
| -
|
| -
|
| -class Record_PTR(SimpleRecord):
|
| - """
|
| - A domain name pointer.
|
| - """
|
| - TYPE = PTR
|
| -
|
| -
|
| -
|
| -class Record_DNAME(SimpleRecord):
|
| - """
|
| - A non-terminal DNS name redirection.
|
| -
|
| - This record type provides the capability to map an entire subtree of the
|
| - DNS name space to another domain. It differs from the CNAME record which
|
| - maps a single node of the name space.
|
| -
|
| - @see: U{http://www.faqs.org/rfcs/rfc2672.html}
|
| - """
|
| - TYPE = DNAME
|
| -
|
| -
|
| -
|
| -class Record_A(tputil.FancyEqMixin):
|
| - """
|
| - An IPv4 host address.
|
| -
|
| - @type address: C{str}
|
| - @ivar address: The packed network-order representation of the IPv4 address
|
| - associated with this record.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| -
|
| - compareAttributes = ('address', 'ttl')
|
| -
|
| - TYPE = A
|
| - address = None
|
| -
|
| - def __init__(self, address='0.0.0.0', ttl=None):
|
| - address = socket.inet_aton(address)
|
| - self.address = address
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(self.address)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.address = readPrecisely(strio, 4)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash(self.address)
|
| -
|
| -
|
| - def __str__(self):
|
| - return '<A %s ttl=%s>' % (self.dottedQuad(), self.ttl)
|
| -
|
| -
|
| - def dottedQuad(self):
|
| - return socket.inet_ntoa(self.address)
|
| -
|
| -
|
| -
|
| -class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
|
| - """
|
| - Marks the start of a zone of authority.
|
| -
|
| - This record describes parameters which are shared by all records within a
|
| - particular zone.
|
| -
|
| - @type mname: L{Name}
|
| - @ivar mname: The domain-name of the name server that was the original or
|
| - primary source of data for this zone.
|
| -
|
| - @type rname: L{Name}
|
| - @ivar rname: A domain-name which specifies the mailbox of the person
|
| - responsible for this zone.
|
| -
|
| - @type serial: C{int}
|
| - @ivar serial: The unsigned 32 bit version number of the original copy of
|
| - the zone. Zone transfers preserve this value. This value wraps and
|
| - should be compared using sequence space arithmetic.
|
| -
|
| - @type refresh: C{int}
|
| - @ivar refresh: A 32 bit time interval before the zone should be refreshed.
|
| -
|
| - @type minimum: C{int}
|
| - @ivar minimum: The unsigned 32 bit minimum TTL field that should be
|
| - exported with any RR from this zone.
|
| -
|
| - @type expire: C{int}
|
| - @ivar expire: A 32 bit time value that specifies the upper limit on the
|
| - time interval that can elapse before the zone is no longer
|
| - authoritative.
|
| -
|
| - @type retry: C{int}
|
| - @ivar retry: A 32 bit time interval that should elapse before a failed
|
| - refresh should be retried.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The default TTL to use for records served from this zone.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| -
|
| - compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'ttl')
|
| - showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
|
| -
|
| - TYPE = SOA
|
| -
|
| - def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=0, minimum=0, ttl=None):
|
| - self.mname, self.rname = Name(mname), Name(rname)
|
| - self.serial, self.refresh = str2time(serial), str2time(refresh)
|
| - self.minimum, self.expire = str2time(minimum), str2time(expire)
|
| - self.retry = str2time(retry)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - self.mname.encode(strio, compDict)
|
| - self.rname.encode(strio, compDict)
|
| - strio.write(
|
| - struct.pack(
|
| - '!LlllL',
|
| - self.serial, self.refresh, self.retry, self.expire,
|
| - self.minimum
|
| - )
|
| - )
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.mname, self.rname = Name(), Name()
|
| - self.mname.decode(strio)
|
| - self.rname.decode(strio)
|
| - r = struct.unpack('!LlllL', readPrecisely(strio, 20))
|
| - self.serial, self.refresh, self.retry, self.expire, self.minimum = r
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((
|
| - self.serial, self.mname, self.rname,
|
| - self.refresh, self.expire, self.retry
|
| - ))
|
| -
|
| -
|
| -
|
| -class Record_NULL: # EXPERIMENTAL
|
| - """
|
| - A null record.
|
| -
|
| - This is an experimental record type.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| -
|
| - TYPE = NULL
|
| -
|
| - def __init__(self, payload=None, ttl=None):
|
| - self.payload = payload
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(self.payload)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.payload = readPrecisely(strio, length)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash(self.payload)
|
| -
|
| -
|
| -
|
| -class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin): # OBSOLETE
|
| - """
|
| - A well known service description.
|
| -
|
| - This record type is obsolete. See L{Record_SRV}.
|
| -
|
| - @type address: C{str}
|
| - @ivar address: The packed network-order representation of the IPv4 address
|
| - associated with this record.
|
| -
|
| - @type protocol: C{int}
|
| - @ivar protocol: The 8 bit IP protocol number for which this service map is
|
| - relevant.
|
| -
|
| - @type map: C{str}
|
| - @ivar map: A bitvector indicating the services available at the specified
|
| - address.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| -
|
| - compareAttributes = ('address', 'protocol', 'map', 'ttl')
|
| - showAttributes = ('address', 'protocol', 'ttl')
|
| -
|
| - TYPE = WKS
|
| -
|
| - def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
|
| - self.address = socket.inet_aton(address)
|
| - self.protocol, self.map = protocol, map
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(self.address)
|
| - strio.write(struct.pack('!B', self.protocol))
|
| - strio.write(self.map)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.address = readPrecisely(strio, 4)
|
| - self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
|
| - self.map = readPrecisely(strio, length - 5)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.address, self.protocol, self.map))
|
| -
|
| -
|
| -
|
| -class Record_AAAA(tputil.FancyEqMixin): # OBSOLETE (or headed there)
|
| - """
|
| - An IPv6 host address.
|
| -
|
| - This record type is obsolete.
|
| -
|
| - @type address: C{str}
|
| - @ivar address: The packed network-order representation of the IPv6 address
|
| - associated with this record.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| -
|
| - @see: L{Record_A6}
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = AAAA
|
| -
|
| - compareAttributes = ('address', 'ttl')
|
| -
|
| - def __init__(self, address = '::', ttl=None):
|
| - self.address = socket.inet_pton(AF_INET6, address)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(self.address)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.address = readPrecisely(strio, 16)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash(self.address)
|
| -
|
| -
|
| - def __str__(self):
|
| - return '<AAAA %s ttl=%s>' % (socket.inet_ntop(AF_INET6, self.address), self.ttl)
|
| -
|
| -
|
| -
|
| -class Record_A6:
|
| - """
|
| - An IPv6 address.
|
| -
|
| - @type prefixLen: C{int}
|
| - @ivar prefixLen: The length of the suffix.
|
| -
|
| - @type suffix: C{str}
|
| - @ivar suffix: An IPv6 address suffix in network order.
|
| -
|
| - @type prefix: L{Name}
|
| - @ivar prefix: If specified, a name which will be used as a prefix for other
|
| - A6 records.
|
| -
|
| - @type bytes: C{int}
|
| - @ivar bytes: The length of the prefix.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| -
|
| - @see: U{http://www.faqs.org/rfcs/rfc2874.html}
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = A6
|
| -
|
| - def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
|
| - self.prefixLen = prefixLen
|
| - self.suffix = socket.inet_pton(AF_INET6, suffix)
|
| - self.prefix = Name(prefix)
|
| - self.bytes = int((128 - self.prefixLen) / 8.0)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(struct.pack('!B', self.prefixLen))
|
| - if self.bytes:
|
| - strio.write(self.suffix[-self.bytes:])
|
| - if self.prefixLen:
|
| - # This may not be compressed
|
| - self.prefix.encode(strio, None)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
|
| - self.bytes = int((128 - self.prefixLen) / 8.0)
|
| - if self.bytes:
|
| - self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
|
| - if self.prefixLen:
|
| - self.prefix.decode(strio)
|
| -
|
| -
|
| - def __eq__(self, other):
|
| - if isinstance(other, Record_A6):
|
| - return (self.prefixLen == other.prefixLen and
|
| - self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
|
| - self.prefix == other.prefix and
|
| - self.ttl == other.ttl)
|
| - return 0
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
|
| -
|
| -
|
| - def __str__(self):
|
| - return '<A6 %s %s (%d) ttl=%s>' % (
|
| - self.prefix,
|
| - socket.inet_ntop(AF_INET6, self.suffix),
|
| - self.prefixLen, self.ttl
|
| - )
|
| -
|
| -
|
| -
|
| -class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
|
| - """
|
| - The location of the server(s) for a specific protocol and domain.
|
| -
|
| - This is an experimental record type.
|
| -
|
| - @type priority: C{int}
|
| - @ivar priority: The priority of this target host. A client MUST attempt to
|
| - contact the target host with the lowest-numbered priority it can reach;
|
| - target hosts with the same priority SHOULD be tried in an order defined
|
| - by the weight field.
|
| -
|
| - @type weight: C{int}
|
| - @ivar weight: Specifies a relative weight for entries with the same
|
| - priority. Larger weights SHOULD be given a proportionately higher
|
| - probability of being selected.
|
| -
|
| - @type port: C{int}
|
| - @ivar port: The port on this target host of this service.
|
| -
|
| - @type target: L{Name}
|
| - @ivar target: The domain name of the target host. There MUST be one or
|
| - more address records for this name, the name MUST NOT be an alias (in
|
| - the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
|
| - required, to return the address record(s) in the Additional Data
|
| - section. Unless and until permitted by future standards action, name
|
| - compression is not to be used for this field.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| -
|
| - @see: U{http://www.faqs.org/rfcs/rfc2782.html}
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = SRV
|
| -
|
| - compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
|
| - showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
|
| -
|
| - def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
|
| - self.priority = int(priority)
|
| - self.weight = int(weight)
|
| - self.port = int(port)
|
| - self.target = Name(target)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
|
| - # This can't be compressed
|
| - self.target.encode(strio, None)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
|
| - self.priority, self.weight, self.port = r
|
| - self.target = Name()
|
| - self.target.decode(strio)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.priority, self.weight, self.port, self.target))
|
| -
|
| -
|
| -
|
| -class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
|
| - """
|
| - Map from a domain name to the name of an AFS cell database server.
|
| -
|
| - @type subtype: C{int}
|
| - @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
|
| - Volume Location Server for the named AFS cell. In the case of subtype
|
| - 2, the host has an authenticated name server holding the cell-root
|
| - directory node for the named DCE/NCA cell.
|
| -
|
| - @type hostname: L{Name}
|
| - @ivar hostname: The domain name of a host that has a server for the cell
|
| - named by this record.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| -
|
| - @see: U{http://www.faqs.org/rfcs/rfc1183.html}
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = AFSDB
|
| -
|
| - compareAttributes = ('subtype', 'hostname', 'ttl')
|
| - showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
|
| -
|
| - def __init__(self, subtype=0, hostname='', ttl=None):
|
| - self.subtype = int(subtype)
|
| - self.hostname = Name(hostname)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(struct.pack('!H', self.subtype))
|
| - self.hostname.encode(strio, compDict)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
|
| - self.subtype, = r
|
| - self.hostname.decode(strio)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.subtype, self.hostname))
|
| -
|
| -
|
| -
|
| -class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
|
| - """
|
| - The responsible person for a domain.
|
| -
|
| - @type mbox: L{Name}
|
| - @ivar mbox: A domain name that specifies the mailbox for the responsible
|
| - person.
|
| -
|
| - @type txt: L{Name}
|
| - @ivar txt: A domain name for which TXT RR's exist (indirection through
|
| - which allows information sharing about the contents of this RP record).
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| -
|
| - @see: U{http://www.faqs.org/rfcs/rfc1183.html}
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = RP
|
| -
|
| - compareAttributes = ('mbox', 'txt', 'ttl')
|
| - showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
|
| -
|
| - def __init__(self, mbox='', txt='', ttl=None):
|
| - self.mbox = Name(mbox)
|
| - self.txt = Name(txt)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - self.mbox.encode(strio, compDict)
|
| - self.txt.encode(strio, compDict)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.mbox = Name()
|
| - self.txt = Name()
|
| - self.mbox.decode(strio)
|
| - self.txt.decode(strio)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.mbox, self.txt))
|
| -
|
| -
|
| -
|
| -class Record_HINFO(tputil.FancyStrMixin):
|
| - """
|
| - Host information.
|
| -
|
| - @type cpu: C{str}
|
| - @ivar cpu: Specifies the CPU type.
|
| -
|
| - @type os: C{str}
|
| - @ivar os: Specifies the OS.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = HINFO
|
| -
|
| - showAttributes = ('cpu', 'os', 'ttl')
|
| -
|
| - def __init__(self, cpu='', os='', ttl=None):
|
| - self.cpu, self.os = cpu, os
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
|
| - strio.write(struct.pack('!B', len(self.os)) + self.os)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
|
| - self.cpu = readPrecisely(strio, cpu)
|
| - os = struct.unpack('!B', readPrecisely(strio, 1))[0]
|
| - self.os = readPrecisely(strio, os)
|
| -
|
| -
|
| - def __eq__(self, other):
|
| - if isinstance(other, Record_HINFO):
|
| - return (self.os.lower() == other.os.lower() and
|
| - self.cpu.lower() == other.cpu.lower() and
|
| - self.ttl == other.ttl)
|
| - return 0
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.os.lower(), self.cpu.lower()))
|
| -
|
| -
|
| -
|
| -class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
|
| - """
|
| - Mailbox or mail list information.
|
| -
|
| - This is an experimental record type.
|
| -
|
| - @type rmailbx: L{Name}
|
| - @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
|
| - for the mailing list or mailbox. If this domain name names the root,
|
| - the owner of the MINFO RR is responsible for itself.
|
| -
|
| - @type emailbx: L{Name}
|
| - @ivar emailbx: A domain-name which specifies a mailbox which is to receive
|
| - error messages related to the mailing list or mailbox specified by the
|
| - owner of the MINFO record. If this domain name names the root, errors
|
| - should be returned to the sender of the message.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = MINFO
|
| -
|
| - rmailbx = None
|
| - emailbx = None
|
| -
|
| - compareAttributes = ('rmailbx', 'emailbx', 'ttl')
|
| - showAttributes = (('rmailbx', 'responsibility', '%s'),
|
| - ('emailbx', 'errors', '%s'),
|
| - 'ttl')
|
| -
|
| - def __init__(self, rmailbx='', emailbx='', ttl=None):
|
| - self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
|
| - self.ttl = str2time(ttl)
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - self.rmailbx.encode(strio, compDict)
|
| - self.emailbx.encode(strio, compDict)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.rmailbx, self.emailbx = Name(), Name()
|
| - self.rmailbx.decode(strio)
|
| - self.emailbx.decode(strio)
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash((self.rmailbx, self.emailbx))
|
| -
|
| -
|
| -
|
| -class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
|
| - """
|
| - Mail exchange.
|
| -
|
| - @type preference: C{int}
|
| - @ivar preference: Specifies the preference given to this RR among others at
|
| - the same owner. Lower values are preferred.
|
| -
|
| - @type name: L{Name}
|
| - @ivar name: A domain-name which specifies a host willing to act as a mail
|
| - exchange.
|
| -
|
| - @type ttl: C{int}
|
| - @ivar ttl: The maximum number of seconds which this record should be
|
| - cached.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| - TYPE = MX
|
| -
|
| - compareAttributes = ('preference', 'name', 'ttl')
|
| - showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
|
| -
|
| - def __init__(self, preference=0, name='', ttl=None, **kwargs):
|
| - self.preference, self.name = int(preference), Name(kwargs.get('exchange', name))
|
| - self.ttl = str2time(ttl)
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - strio.write(struct.pack('!H', self.preference))
|
| - self.name.encode(strio, compDict)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
|
| - self.name = Name()
|
| - self.name.decode(strio)
|
| -
|
| - def exchange(self):
|
| - warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklevel=2)
|
| - return self.name
|
| -
|
| - exchange = property(exchange)
|
| -
|
| - def __hash__(self):
|
| - return hash((self.preference, self.name))
|
| -
|
| -
|
| -
|
| -# Oh god, Record_TXT how I hate thee.
|
| -class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
|
| - """
|
| - Freeform text.
|
| -
|
| - @type data: C{list} of C{str}
|
| - @ivar data: Freeform text which makes up this record.
|
| - """
|
| - implements(IEncodable, IRecord)
|
| -
|
| - TYPE = TXT
|
| -
|
| - showAttributes = compareAttributes = ('data', 'ttl')
|
| -
|
| - def __init__(self, *data, **kw):
|
| - self.data = list(data)
|
| - # arg man python sucks so bad
|
| - self.ttl = str2time(kw.get('ttl', None))
|
| -
|
| -
|
| - def encode(self, strio, compDict = None):
|
| - for d in self.data:
|
| - strio.write(struct.pack('!B', len(d)) + d)
|
| -
|
| -
|
| - def decode(self, strio, length = None):
|
| - soFar = 0
|
| - self.data = []
|
| - while soFar < length:
|
| - L = struct.unpack('!B', readPrecisely(strio, 1))[0]
|
| - self.data.append(readPrecisely(strio, L))
|
| - soFar += L + 1
|
| - if soFar != length:
|
| - log.msg(
|
| - "Decoded %d bytes in TXT record, but rdlength is %d" % (
|
| - soFar, length
|
| - )
|
| - )
|
| -
|
| -
|
| - def __hash__(self):
|
| - return hash(tuple(self.data))
|
| -
|
| -
|
| -
|
| -class Message:
|
| - headerFmt = "!H2B4H"
|
| - headerSize = struct.calcsize(headerFmt)
|
| -
|
| - # Question, answer, additional, and nameserver lists
|
| - queries = answers = add = ns = None
|
| -
|
| - def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
|
| - auth=0, rCode=OK, trunc=0, maxSize=512):
|
| - self.maxSize = maxSize
|
| - self.id = id
|
| - self.answer = answer
|
| - self.opCode = opCode
|
| - self.auth = auth
|
| - self.trunc = trunc
|
| - self.recDes = recDes
|
| - self.recAv = recAv
|
| - self.rCode = rCode
|
| - self.queries = []
|
| - self.answers = []
|
| - self.authority = []
|
| - self.additional = []
|
| -
|
| -
|
| - def addQuery(self, name, type=ALL_RECORDS, cls=IN):
|
| - """
|
| - Add another query to this Message.
|
| -
|
| - @type name: C{str}
|
| - @param name: The name to query.
|
| -
|
| - @type type: C{int}
|
| - @param type: Query type
|
| -
|
| - @type cls: C{int}
|
| - @param cls: Query class
|
| - """
|
| - self.queries.append(Query(name, type, cls))
|
| -
|
| -
|
| - def encode(self, strio):
|
| - compDict = {}
|
| - body_tmp = StringIO.StringIO()
|
| - for q in self.queries:
|
| - q.encode(body_tmp, compDict)
|
| - for q in self.answers:
|
| - q.encode(body_tmp, compDict)
|
| - for q in self.authority:
|
| - q.encode(body_tmp, compDict)
|
| - for q in self.additional:
|
| - q.encode(body_tmp, compDict)
|
| - body = body_tmp.getvalue()
|
| - size = len(body) + self.headerSize
|
| - if self.maxSize and size > self.maxSize:
|
| - self.trunc = 1
|
| - body = body[:self.maxSize - self.headerSize]
|
| - byte3 = (( ( self.answer & 1 ) << 7 )
|
| - | ((self.opCode & 0xf ) << 3 )
|
| - | ((self.auth & 1 ) << 2 )
|
| - | ((self.trunc & 1 ) << 1 )
|
| - | ( self.recDes & 1 ) )
|
| - byte4 = ( ( (self.recAv & 1 ) << 7 )
|
| - | (self.rCode & 0xf ) )
|
| -
|
| - strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
|
| - len(self.queries), len(self.answers),
|
| - len(self.authority), len(self.additional)))
|
| - strio.write(body)
|
| -
|
| -
|
| - def decode(self, strio, length=None):
|
| - self.maxSize = 0
|
| - header = readPrecisely(strio, self.headerSize)
|
| - r = struct.unpack(self.headerFmt, header)
|
| - self.id, byte3, byte4, nqueries, nans, nns, nadd = r
|
| - self.answer = ( byte3 >> 7 ) & 1
|
| - self.opCode = ( byte3 >> 3 ) & 0xf
|
| - self.auth = ( byte3 >> 2 ) & 1
|
| - self.trunc = ( byte3 >> 1 ) & 1
|
| - self.recDes = byte3 & 1
|
| - self.recAv = ( byte4 >> 7 ) & 1
|
| - self.rCode = byte4 & 0xf
|
| -
|
| - self.queries = []
|
| - for i in range(nqueries):
|
| - q = Query()
|
| - try:
|
| - q.decode(strio)
|
| - except EOFError:
|
| - return
|
| - self.queries.append(q)
|
| -
|
| - items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
|
| - for (l, n) in items:
|
| - self.parseRecords(l, n, strio)
|
| -
|
| -
|
| - def parseRecords(self, list, num, strio):
|
| - for i in range(num):
|
| - header = RRHeader()
|
| - try:
|
| - header.decode(strio)
|
| - except EOFError:
|
| - return
|
| - t = self.lookupRecordType(header.type)
|
| - if not t:
|
| - continue
|
| - header.payload = t(ttl=header.ttl)
|
| - try:
|
| - header.payload.decode(strio, header.rdlength)
|
| - except EOFError:
|
| - return
|
| - list.append(header)
|
| -
|
| -
|
| - def lookupRecordType(self, type):
|
| - return globals().get('Record_' + QUERY_TYPES.get(type, ''), None)
|
| -
|
| -
|
| - def toStr(self):
|
| - strio = StringIO.StringIO()
|
| - self.encode(strio)
|
| - return strio.getvalue()
|
| -
|
| -
|
| - def fromStr(self, str):
|
| - strio = StringIO.StringIO(str)
|
| - self.decode(strio)
|
| -
|
| -class DNSMixin(object):
|
| - """
|
| - DNS protocol mixin shared by UDP and TCP implementations.
|
| - """
|
| - id = None
|
| - liveMessages = None
|
| -
|
| - def __init__(self, controller):
|
| - self.controller = controller
|
| - self.id = random.randrange(2 ** 10, 2 ** 15)
|
| -
|
| - def pickID(self):
|
| - """
|
| - Return a unique ID for queries.
|
| - """
|
| - while True:
|
| - self.id += randomSource() % (2 ** 10)
|
| - self.id %= 2 ** 16
|
| - if self.id not in self.liveMessages:
|
| - break
|
| - return self.id
|
| -
|
| - def callLater(self, period, func, *args):
|
| - """
|
| - Wrapper around reactor.callLater, mainly for test purpose.
|
| - """
|
| - from twisted.internet import reactor
|
| - return reactor.callLater(period, func, *args)
|
| -
|
| - def _query(self, queries, timeout, id, writeMessage):
|
| - """
|
| - Send out a message with the given queries.
|
| -
|
| - @type queries: C{list} of C{Query} instances
|
| - @param queries: The queries to transmit
|
| -
|
| - @type timeout: C{int} or C{float}
|
| - @param timeout: How long to wait before giving up
|
| -
|
| - @type id: C{int}
|
| - @param id: Unique key for this request
|
| -
|
| - @type writeMessage: C{callable}
|
| - @param writeMessage: One-parameter callback which writes the message
|
| -
|
| - @rtype: C{Deferred}
|
| - @return: a C{Deferred} which will be fired with the result of the
|
| - query, or errbacked with any errors that could happen (exceptions
|
| - during writing of the query, timeout errors, ...).
|
| - """
|
| - m = Message(id, recDes=1)
|
| - m.queries = queries
|
| -
|
| - try:
|
| - writeMessage(m)
|
| - except:
|
| - return defer.fail()
|
| -
|
| - resultDeferred = defer.Deferred()
|
| - cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
|
| - self.liveMessages[id] = (resultDeferred, cancelCall)
|
| -
|
| - return resultDeferred
|
| -
|
| - def _clearFailed(self, deferred, id):
|
| - """
|
| - Clean the Deferred after a timeout.
|
| - """
|
| - try:
|
| - del self.liveMessages[id]
|
| - except KeyError:
|
| - pass
|
| - deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
|
| -
|
| -
|
| -class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
|
| - """
|
| - DNS protocol over UDP.
|
| - """
|
| - resends = None
|
| -
|
| - def stopProtocol(self):
|
| - """
|
| - Stop protocol: reset state variables.
|
| - """
|
| - self.liveMessages = {}
|
| - self.resends = {}
|
| - self.transport = None
|
| -
|
| - def startProtocol(self):
|
| - """
|
| - Upon start, reset internal state.
|
| - """
|
| - self.liveMessages = {}
|
| - self.resends = {}
|
| -
|
| - def writeMessage(self, message, address):
|
| - """
|
| - Send a message holding DNS queries.
|
| -
|
| - @type message: L{Message}
|
| - """
|
| - self.transport.write(message.toStr(), address)
|
| -
|
| - def startListening(self):
|
| - from twisted.internet import reactor
|
| - reactor.listenUDP(0, self, maxPacketSize=512)
|
| -
|
| - def datagramReceived(self, data, addr):
|
| - """
|
| - Read a datagram, extract the message in it and trigger the associated
|
| - Deferred.
|
| - """
|
| - m = Message()
|
| - try:
|
| - m.fromStr(data)
|
| - except EOFError:
|
| - log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
|
| - return
|
| - except:
|
| - # Nothing should trigger this, but since we're potentially
|
| - # invoking a lot of different decoding methods, we might as well
|
| - # be extra cautious. Anything that triggers this is itself
|
| - # buggy.
|
| - log.err(failure.Failure(), "Unexpected decoding error")
|
| - return
|
| -
|
| - if m.id in self.liveMessages:
|
| - d, canceller = self.liveMessages[m.id]
|
| - del self.liveMessages[m.id]
|
| - canceller.cancel()
|
| - # XXX we shouldn't need this hack of catching exception on callback()
|
| - try:
|
| - d.callback(m)
|
| - except:
|
| - log.err()
|
| - else:
|
| - if m.id not in self.resends:
|
| - self.controller.messageReceived(m, self, addr)
|
| -
|
| -
|
| - def removeResend(self, id):
|
| - """
|
| - Mark message ID as no longer having duplication suppression.
|
| - """
|
| - try:
|
| - del self.resends[id]
|
| - except KeyError:
|
| - pass
|
| -
|
| - def query(self, address, queries, timeout=10, id=None):
|
| - """
|
| - Send out a message with the given queries.
|
| -
|
| - @type address: C{tuple} of C{str} and C{int}
|
| - @param address: The address to which to send the query
|
| -
|
| - @type queries: C{list} of C{Query} instances
|
| - @param queries: The queries to transmit
|
| -
|
| - @rtype: C{Deferred}
|
| - """
|
| - if not self.transport:
|
| - # XXX transport might not get created automatically, use callLater?
|
| - try:
|
| - self.startListening()
|
| - except CannotListenError:
|
| - return defer.fail()
|
| -
|
| - if id is None:
|
| - id = self.pickID()
|
| - else:
|
| - self.resends[id] = 1
|
| -
|
| - def writeMessage(m):
|
| - self.writeMessage(m, address)
|
| -
|
| - return self._query(queries, timeout, id, writeMessage)
|
| -
|
| -
|
| -class DNSProtocol(DNSMixin, protocol.Protocol):
|
| - """
|
| - DNS protocol over TCP.
|
| - """
|
| - length = None
|
| - buffer = ''
|
| -
|
| - def writeMessage(self, message):
|
| - """
|
| - Send a message holding DNS queries.
|
| -
|
| - @type message: L{Message}
|
| - """
|
| - s = message.toStr()
|
| - self.transport.write(struct.pack('!H', len(s)) + s)
|
| -
|
| - def connectionMade(self):
|
| - """
|
| - Connection is made: reset internal state, and notify the controller.
|
| - """
|
| - self.liveMessages = {}
|
| - self.controller.connectionMade(self)
|
| -
|
| - def dataReceived(self, data):
|
| - self.buffer += data
|
| -
|
| - while self.buffer:
|
| - if self.length is None and len(self.buffer) >= 2:
|
| - self.length = struct.unpack('!H', self.buffer[:2])[0]
|
| - self.buffer = self.buffer[2:]
|
| -
|
| - if len(self.buffer) >= self.length:
|
| - myChunk = self.buffer[:self.length]
|
| - m = Message()
|
| - m.fromStr(myChunk)
|
| -
|
| - try:
|
| - d, canceller = self.liveMessages[m.id]
|
| - except KeyError:
|
| - self.controller.messageReceived(m, self)
|
| - else:
|
| - del self.liveMessages[m.id]
|
| - canceller.cancel()
|
| - # XXX we shouldn't need this hack
|
| - try:
|
| - d.callback(m)
|
| - except:
|
| - log.err()
|
| -
|
| - self.buffer = self.buffer[self.length:]
|
| - self.length = None
|
| - else:
|
| - break
|
| -
|
| - def query(self, queries, timeout=60):
|
| - """
|
| - Send out a message with the given queries.
|
| -
|
| - @type queries: C{list} of C{Query} instances
|
| - @param queries: The queries to transmit
|
| -
|
| - @rtype: C{Deferred}
|
| - """
|
| - id = self.pickID()
|
| - return self._query(queries, timeout, id, self.writeMessage)
|
| -
|
|
|