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) |
- |