1 # -*- test-case-name: twisted.names.test.test_dns -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 DNS protocol implementation.
9 - Get rid of some toplevels, maybe.
12 @author: Jean-Paul Calderone
16 'IEncodable', 'IRecord',
18 'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'HINFO',
19 'MAILA', 'MAILB', 'MB', 'MD', 'MF', 'MG', 'MINFO', 'MR', 'MX',
20 'NAPTR', 'NS', 'NULL', 'PTR', 'RP', 'SOA', 'SPF', 'SRV', 'TXT', 'WKS',
22 'ANY', 'CH', 'CS', 'HS', 'IN',
24 'ALL_RECORDS', 'AXFR', 'IXFR',
26 'EFORMAT', 'ENAME', 'ENOTIMP', 'EREFUSED', 'ESERVER',
28 'Record_A', 'Record_A6', 'Record_AAAA', 'Record_AFSDB', 'Record_CNAME',
29 'Record_DNAME', 'Record_HINFO', 'Record_MB', 'Record_MD', 'Record_MF',
30 'Record_MG', 'Record_MINFO', 'Record_MR', 'Record_MX', 'Record_NAPTR',
31 'Record_NS', 'Record_NULL', 'Record_PTR', 'Record_RP', 'Record_SOA',
32 'Record_SPF', 'Record_SRV', 'Record_TXT', 'Record_WKS', 'UnknownRecord',
34 'QUERY_CLASSES', 'QUERY_TYPES', 'REV_CLASSES', 'REV_TYPES', 'EXT_QUERIES',
36 'Charstr', 'Message', 'Name', 'Query', 'RRHeader', 'SimpleRecord',
37 'DNSDatagramProtocol', 'DNSMixin', 'DNSProtocol',
39 'OK', 'OP_INVERSE', 'OP_NOTIFY', 'OP_QUERY', 'OP_STATUS', 'OP_UPDATE',
42 'AuthoritativeDomainError', 'DNSQueryTimeoutError', 'DomainError',
49 import struct, random, types, socket
51 import cStringIO as StringIO
53 AF_INET6 = socket.AF_INET6
55 from zope.interface import implements, Interface, Attribute
59 from twisted.internet import protocol, defer
60 from twisted.internet.error import CannotListenError
61 from twisted.python import log, failure
62 from twisted.python import util as tputil
63 from twisted.python import randbytes
68 Wrapper around L{randbytes.secureRandom} to return 2 random chars.
70 return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
75 (A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
76 RP, AFSDB) = range(1, 19)
104 # 19 through 27? Eh, I'll get to 'em.
114 IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
116 # "Extended" queries (Hey, half of these are deprecated, good job)
122 ALL_RECORDS: 'ALL_RECORDS'
126 (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
129 IN, CS, CH, HS = range(1, 5)
140 (v, k) for (k, v) in QUERY_CLASSES.items()
145 OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
146 OP_NOTIFY = 4 # RFC 1996
147 OP_UPDATE = 5 # RFC 2136
151 OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
153 class IRecord(Interface):
155 An single entry in a zone of authority.
158 TYPE = Attribute("An indicator of what kind of record this is.")
161 # Backwards compatibility aliases - these should be deprecated or something I
163 from twisted.names.error import DomainError, AuthoritativeDomainError
164 from twisted.names.error import DNSQueryTimeoutError
169 ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
170 ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
172 if isinstance(s, types.StringType):
173 s = s.upper().strip()
174 for (suff, mult) in suffixes:
176 return int(float(s[:-1]) * mult)
180 raise ValueError, "Invalid time interval specifier: " + s
184 def readPrecisely(file, l):
191 class IEncodable(Interface):
193 Interface for something which can be encoded to and decoded
197 def encode(strio, compDict = None):
199 Write a representation of this object to the given
202 @type strio: File-like object
203 @param strio: The stream to which to write bytes
205 @type compDict: C{dict} or C{None}
206 @param compDict: A dictionary of backreference addresses that have
207 have already been written to this stream and that may be used for
211 def decode(strio, length = None):
213 Reconstruct an object from data read from the given
216 @type strio: File-like object
217 @param strio: The stream from which bytes may be read
219 @type length: C{int} or C{None}
220 @param length: The number of bytes in this RDATA field. Most
221 implementations can ignore this value. Only in the case of
222 records similar to TXT where the total length is in no way
223 encoded in the data is it necessary.
228 class Charstr(object):
229 implements(IEncodable)
231 def __init__(self, string=''):
232 if not isinstance(string, str):
233 raise ValueError("%r is not a string" % (string,))
237 def encode(self, strio, compDict=None):
239 Encode this Character string into the appropriate byte format.
242 @param strio: The byte representation of this Charstr will be written
247 strio.write(chr(ind))
251 def decode(self, strio, length=None):
253 Decode a byte string into this Name.
256 @param strio: Bytes will be read from this file until the full string
259 @raise EOFError: Raised when there are not enough bytes available from
263 l = ord(readPrecisely(strio, 1))
264 self.string = readPrecisely(strio, l)
267 def __eq__(self, other):
268 if isinstance(other, Charstr):
269 return self.string == other.string
274 return hash(self.string)
283 implements(IEncodable)
285 def __init__(self, name=''):
286 assert isinstance(name, types.StringTypes), "%r is not a string" % (name,)
289 def encode(self, strio, compDict=None):
291 Encode this Name into the appropriate byte format.
294 @param strio: The byte representation of this Name will be written to
298 @param compDict: dictionary of Names that have already been encoded
299 and whose addresses may be backreferenced by this Name (for the purpose
300 of reducing the message size).
304 if compDict is not None:
307 struct.pack("!H", 0xc000 | compDict[name]))
310 compDict[name] = strio.tell() + Message.headerSize
313 label, name = name[:ind], name[ind + 1:]
315 label, name = name, ''
317 strio.write(chr(ind))
322 def decode(self, strio, length=None):
324 Decode a byte string into this Name.
327 @param strio: Bytes will be read from this file until the full Name
330 @raise EOFError: Raised when there are not enough bytes available
333 @raise ValueError: Raised when the name cannot be decoded (for example,
334 because it contains a loop).
340 l = ord(readPrecisely(strio, 1))
346 new_off = ((l&63) << 8
347 | ord(readPrecisely(strio, 1)))
348 if new_off in visited:
349 raise ValueError("Compression loop in encoded name")
355 label = readPrecisely(strio, l)
359 self.name = self.name + '.' + label
361 def __eq__(self, other):
362 if isinstance(other, Name):
363 return str(self) == str(other)
368 return hash(str(self))
376 Represent a single DNS query.
378 @ivar name: The name about which this query is requesting information.
379 @ivar type: The query type.
380 @ivar cls: The query class.
383 implements(IEncodable)
389 def __init__(self, name='', type=A, cls=IN):
392 @param name: The name about which to request information.
395 @param type: The query type.
398 @param cls: The query class.
400 self.name = Name(name)
405 def encode(self, strio, compDict=None):
406 self.name.encode(strio, compDict)
407 strio.write(struct.pack("!HH", self.type, self.cls))
410 def decode(self, strio, length = None):
411 self.name.decode(strio)
412 buff = readPrecisely(strio, 4)
413 self.type, self.cls = struct.unpack("!HH", buff)
417 return hash((str(self.name).lower(), self.type, self.cls))
420 def __cmp__(self, other):
421 return isinstance(other, Query) and cmp(
422 (str(self.name).lower(), self.type, self.cls),
423 (str(other.name).lower(), other.type, other.cls)
424 ) or cmp(self.__class__, other.__class__)
428 t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
429 c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
430 return '<Query %s %s %s>' % (self.name, t, c)
434 return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
437 class RRHeader(tputil.FancyEqMixin):
439 A resource record header.
441 @cvar fmt: C{str} specifying the byte format of an RR.
443 @ivar name: The name about which this reply contains information.
444 @ivar type: The query type of the original request.
445 @ivar cls: The query class of the original request.
446 @ivar ttl: The time-to-live for this record.
447 @ivar payload: An object that implements the IEncodable interface
449 @ivar auth: A C{bool} indicating whether this C{RRHeader} was parsed from an
450 authoritative message.
453 implements(IEncodable)
455 compareAttributes = ('name', 'type', 'cls', 'ttl', 'payload', 'auth')
466 cachedResponse = None
468 def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False):
471 @param name: The name about which this reply contains information.
474 @param type: The query type.
477 @param cls: The query class.
480 @param ttl: Time to live for this record.
482 @type payload: An object implementing C{IEncodable}
483 @param payload: A Query Type specific data object.
485 assert (payload is None) or isinstance(payload, UnknownRecord) or (payload.TYPE == type)
487 self.name = Name(name)
491 self.payload = payload
495 def encode(self, strio, compDict=None):
496 self.name.encode(strio, compDict)
497 strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
499 prefix = strio.tell()
500 self.payload.encode(strio, compDict)
502 strio.seek(prefix - 2, 0)
503 strio.write(struct.pack('!H', aft - prefix))
507 def decode(self, strio, length = None):
508 self.name.decode(strio)
509 l = struct.calcsize(self.fmt)
510 buff = readPrecisely(strio, l)
511 r = struct.unpack(self.fmt, buff)
512 self.type, self.cls, self.ttl, self.rdlength = r
515 def isAuthoritative(self):
520 t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
521 c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
522 return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
529 class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
531 A Resource Record which consists of a single RFC 1035 domain-name.
534 @ivar name: The name associated with this record.
537 @ivar ttl: The maximum number of seconds which this record should be
540 implements(IEncodable, IRecord)
542 showAttributes = (('name', 'name', '%s'), 'ttl')
543 compareAttributes = ('name', 'ttl')
548 def __init__(self, name='', ttl=None):
549 self.name = Name(name)
550 self.ttl = str2time(ttl)
553 def encode(self, strio, compDict = None):
554 self.name.encode(strio, compDict)
557 def decode(self, strio, length = None):
559 self.name.decode(strio)
563 return hash(self.name)
566 # Kinds of RRs - oh my!
567 class Record_NS(SimpleRecord):
569 An authoritative nameserver.
576 class Record_MD(SimpleRecord):
580 This record type is obsolete.
589 class Record_MF(SimpleRecord):
593 This record type is obsolete.
602 class Record_CNAME(SimpleRecord):
604 The canonical name for an alias.
607 fancybasename = 'CNAME'
611 class Record_MB(SimpleRecord):
613 A mailbox domain name.
615 This is an experimental record type.
622 class Record_MG(SimpleRecord):
626 This is an experimental record type.
633 class Record_MR(SimpleRecord):
635 A mail rename domain name.
637 This is an experimental record type.
644 class Record_PTR(SimpleRecord):
646 A domain name pointer.
649 fancybasename = 'PTR'
653 class Record_DNAME(SimpleRecord):
655 A non-terminal DNS name redirection.
657 This record type provides the capability to map an entire subtree of the
658 DNS name space to another domain. It differs from the CNAME record which
659 maps a single node of the name space.
661 @see: U{http://www.faqs.org/rfcs/rfc2672.html}
662 @see: U{http://www.faqs.org/rfcs/rfc3363.html}
665 fancybasename = 'DNAME'
669 class Record_A(tputil.FancyEqMixin):
671 An IPv4 host address.
673 @type address: C{str}
674 @ivar address: The packed network-order representation of the IPv4 address
675 associated with this record.
678 @ivar ttl: The maximum number of seconds which this record should be
681 implements(IEncodable, IRecord)
683 compareAttributes = ('address', 'ttl')
688 def __init__(self, address='0.0.0.0', ttl=None):
689 address = socket.inet_aton(address)
690 self.address = address
691 self.ttl = str2time(ttl)
694 def encode(self, strio, compDict = None):
695 strio.write(self.address)
698 def decode(self, strio, length = None):
699 self.address = readPrecisely(strio, 4)
703 return hash(self.address)
707 return '<A address=%s ttl=%s>' % (self.dottedQuad(), self.ttl)
711 def dottedQuad(self):
712 return socket.inet_ntoa(self.address)
716 class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
718 Marks the start of a zone of authority.
720 This record describes parameters which are shared by all records within a
724 @ivar mname: The domain-name of the name server that was the original or
725 primary source of data for this zone.
728 @ivar rname: A domain-name which specifies the mailbox of the person
729 responsible for this zone.
732 @ivar serial: The unsigned 32 bit version number of the original copy of
733 the zone. Zone transfers preserve this value. This value wraps and
734 should be compared using sequence space arithmetic.
736 @type refresh: C{int}
737 @ivar refresh: A 32 bit time interval before the zone should be refreshed.
739 @type minimum: C{int}
740 @ivar minimum: The unsigned 32 bit minimum TTL field that should be
741 exported with any RR from this zone.
744 @ivar expire: A 32 bit time value that specifies the upper limit on the
745 time interval that can elapse before the zone is no longer
749 @ivar retry: A 32 bit time interval that should elapse before a failed
750 refresh should be retried.
753 @ivar ttl: The default TTL to use for records served from this zone.
755 implements(IEncodable, IRecord)
757 fancybasename = 'SOA'
758 compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'minimum', 'ttl')
759 showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
763 def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=0, minimum=0, ttl=None):
764 self.mname, self.rname = Name(mname), Name(rname)
765 self.serial, self.refresh = str2time(serial), str2time(refresh)
766 self.minimum, self.expire = str2time(minimum), str2time(expire)
767 self.retry = str2time(retry)
768 self.ttl = str2time(ttl)
771 def encode(self, strio, compDict = None):
772 self.mname.encode(strio, compDict)
773 self.rname.encode(strio, compDict)
777 self.serial, self.refresh, self.retry, self.expire,
783 def decode(self, strio, length = None):
784 self.mname, self.rname = Name(), Name()
785 self.mname.decode(strio)
786 self.rname.decode(strio)
787 r = struct.unpack('!LlllL', readPrecisely(strio, 20))
788 self.serial, self.refresh, self.retry, self.expire, self.minimum = r
793 self.serial, self.mname, self.rname,
794 self.refresh, self.expire, self.retry
799 class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
803 This is an experimental record type.
806 @ivar ttl: The maximum number of seconds which this record should be
809 implements(IEncodable, IRecord)
811 fancybasename = 'NULL'
812 showAttributes = compareAttributes = ('payload', 'ttl')
816 def __init__(self, payload=None, ttl=None):
817 self.payload = payload
818 self.ttl = str2time(ttl)
821 def encode(self, strio, compDict = None):
822 strio.write(self.payload)
825 def decode(self, strio, length = None):
826 self.payload = readPrecisely(strio, length)
830 return hash(self.payload)
834 class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
836 A well known service description.
838 This record type is obsolete. See L{Record_SRV}.
840 @type address: C{str}
841 @ivar address: The packed network-order representation of the IPv4 address
842 associated with this record.
844 @type protocol: C{int}
845 @ivar protocol: The 8 bit IP protocol number for which this service map is
849 @ivar map: A bitvector indicating the services available at the specified
853 @ivar ttl: The maximum number of seconds which this record should be
856 implements(IEncodable, IRecord)
858 fancybasename = "WKS"
859 compareAttributes = ('address', 'protocol', 'map', 'ttl')
860 showAttributes = [('_address', 'address', '%s'), 'protocol', 'ttl']
864 _address = property(lambda self: socket.inet_ntoa(self.address))
866 def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
867 self.address = socket.inet_aton(address)
868 self.protocol, self.map = protocol, map
869 self.ttl = str2time(ttl)
872 def encode(self, strio, compDict = None):
873 strio.write(self.address)
874 strio.write(struct.pack('!B', self.protocol))
875 strio.write(self.map)
878 def decode(self, strio, length = None):
879 self.address = readPrecisely(strio, 4)
880 self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
881 self.map = readPrecisely(strio, length - 5)
885 return hash((self.address, self.protocol, self.map))
889 class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
891 An IPv6 host address.
893 @type address: C{str}
894 @ivar address: The packed network-order representation of the IPv6 address
895 associated with this record.
898 @ivar ttl: The maximum number of seconds which this record should be
901 @see: U{http://www.faqs.org/rfcs/rfc1886.html}
903 implements(IEncodable, IRecord)
906 fancybasename = 'AAAA'
907 showAttributes = (('_address', 'address', '%s'), 'ttl')
908 compareAttributes = ('address', 'ttl')
910 _address = property(lambda self: socket.inet_ntop(AF_INET6, self.address))
912 def __init__(self, address = '::', ttl=None):
913 self.address = socket.inet_pton(AF_INET6, address)
914 self.ttl = str2time(ttl)
917 def encode(self, strio, compDict = None):
918 strio.write(self.address)
921 def decode(self, strio, length = None):
922 self.address = readPrecisely(strio, 16)
926 return hash(self.address)
930 class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
934 This is an experimental record type.
936 @type prefixLen: C{int}
937 @ivar prefixLen: The length of the suffix.
940 @ivar suffix: An IPv6 address suffix in network order.
942 @type prefix: L{Name}
943 @ivar prefix: If specified, a name which will be used as a prefix for other
947 @ivar bytes: The length of the prefix.
950 @ivar ttl: The maximum number of seconds which this record should be
953 @see: U{http://www.faqs.org/rfcs/rfc2874.html}
954 @see: U{http://www.faqs.org/rfcs/rfc3363.html}
955 @see: U{http://www.faqs.org/rfcs/rfc3364.html}
957 implements(IEncodable, IRecord)
961 showAttributes = (('_suffix', 'suffix', '%s'), ('prefix', 'prefix', '%s'), 'ttl')
962 compareAttributes = ('prefixLen', 'prefix', 'suffix', 'ttl')
964 _suffix = property(lambda self: socket.inet_ntop(AF_INET6, self.suffix))
966 def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
967 self.prefixLen = prefixLen
968 self.suffix = socket.inet_pton(AF_INET6, suffix)
969 self.prefix = Name(prefix)
970 self.bytes = int((128 - self.prefixLen) / 8.0)
971 self.ttl = str2time(ttl)
974 def encode(self, strio, compDict = None):
975 strio.write(struct.pack('!B', self.prefixLen))
977 strio.write(self.suffix[-self.bytes:])
979 # This may not be compressed
980 self.prefix.encode(strio, None)
983 def decode(self, strio, length = None):
984 self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
985 self.bytes = int((128 - self.prefixLen) / 8.0)
987 self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
989 self.prefix.decode(strio)
992 def __eq__(self, other):
993 if isinstance(other, Record_A6):
994 return (self.prefixLen == other.prefixLen and
995 self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
996 self.prefix == other.prefix and
997 self.ttl == other.ttl)
998 return NotImplemented
1002 return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
1006 return '<A6 %s %s (%d) ttl=%s>' % (
1008 socket.inet_ntop(AF_INET6, self.suffix),
1009 self.prefixLen, self.ttl
1014 class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
1016 The location of the server(s) for a specific protocol and domain.
1018 This is an experimental record type.
1020 @type priority: C{int}
1021 @ivar priority: The priority of this target host. A client MUST attempt to
1022 contact the target host with the lowest-numbered priority it can reach;
1023 target hosts with the same priority SHOULD be tried in an order defined
1024 by the weight field.
1026 @type weight: C{int}
1027 @ivar weight: Specifies a relative weight for entries with the same
1028 priority. Larger weights SHOULD be given a proportionately higher
1029 probability of being selected.
1032 @ivar port: The port on this target host of this service.
1034 @type target: L{Name}
1035 @ivar target: The domain name of the target host. There MUST be one or
1036 more address records for this name, the name MUST NOT be an alias (in
1037 the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
1038 required, to return the address record(s) in the Additional Data
1039 section. Unless and until permitted by future standards action, name
1040 compression is not to be used for this field.
1043 @ivar ttl: The maximum number of seconds which this record should be
1046 @see: U{http://www.faqs.org/rfcs/rfc2782.html}
1048 implements(IEncodable, IRecord)
1051 fancybasename = 'SRV'
1052 compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
1053 showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
1055 def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
1056 self.priority = int(priority)
1057 self.weight = int(weight)
1058 self.port = int(port)
1059 self.target = Name(target)
1060 self.ttl = str2time(ttl)
1063 def encode(self, strio, compDict = None):
1064 strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
1065 # This can't be compressed
1066 self.target.encode(strio, None)
1069 def decode(self, strio, length = None):
1070 r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
1071 self.priority, self.weight, self.port = r
1072 self.target = Name()
1073 self.target.decode(strio)
1077 return hash((self.priority, self.weight, self.port, self.target))
1081 class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
1083 The location of the server(s) for a specific protocol and domain.
1086 @ivar order: An integer specifying the order in which the NAPTR records
1087 MUST be processed to ensure the correct ordering of rules. Low numbers
1088 are processed before high numbers.
1090 @type preference: C{int}
1091 @ivar preference: An integer that specifies the order in which NAPTR
1092 records with equal "order" values SHOULD be processed, low numbers
1093 being processed before high numbers.
1095 @type flag: L{Charstr}
1096 @ivar flag: A <character-string> containing flags to control aspects of the
1097 rewriting and interpretation of the fields in the record. Flags
1098 aresingle characters from the set [A-Z0-9]. The case of the alphabetic
1099 characters is not significant.
1101 At this time only four flags, "S", "A", "U", and "P", are defined.
1103 @type service: L{Charstr}
1104 @ivar service: Specifies the service(s) available down this rewrite path.
1105 It may also specify the particular protocol that is used to talk with a
1106 service. A protocol MUST be specified if the flags field states that
1107 the NAPTR is terminal.
1109 @type regexp: L{Charstr}
1110 @ivar regexp: A STRING containing a substitution expression that is applied
1111 to the original string held by the client in order to construct the
1112 next domain name to lookup.
1114 @type replacement: L{Name}
1115 @ivar replacement: The next NAME to query for NAPTR, SRV, or address
1116 records depending on the value of the flags field. This MUST be a
1117 fully qualified domain-name.
1120 @ivar ttl: The maximum number of seconds which this record should be
1123 @see: U{http://www.faqs.org/rfcs/rfc2915.html}
1125 implements(IEncodable, IRecord)
1128 compareAttributes = ('order', 'preference', 'flags', 'service', 'regexp',
1130 fancybasename = 'NAPTR'
1131 showAttributes = ('order', 'preference', ('flags', 'flags', '%s'),
1132 ('service', 'service', '%s'), ('regexp', 'regexp', '%s'),
1133 ('replacement', 'replacement', '%s'), 'ttl')
1135 def __init__(self, order=0, preference=0, flags='', service='', regexp='',
1136 replacement='', ttl=None):
1137 self.order = int(order)
1138 self.preference = int(preference)
1139 self.flags = Charstr(flags)
1140 self.service = Charstr(service)
1141 self.regexp = Charstr(regexp)
1142 self.replacement = Name(replacement)
1143 self.ttl = str2time(ttl)
1146 def encode(self, strio, compDict=None):
1147 strio.write(struct.pack('!HH', self.order, self.preference))
1148 # This can't be compressed
1149 self.flags.encode(strio, None)
1150 self.service.encode(strio, None)
1151 self.regexp.encode(strio, None)
1152 self.replacement.encode(strio, None)
1155 def decode(self, strio, length=None):
1156 r = struct.unpack('!HH', readPrecisely(strio, struct.calcsize('!HH')))
1157 self.order, self.preference = r
1158 self.flags = Charstr()
1159 self.service = Charstr()
1160 self.regexp = Charstr()
1161 self.replacement = Name()
1162 self.flags.decode(strio)
1163 self.service.decode(strio)
1164 self.regexp.decode(strio)
1165 self.replacement.decode(strio)
1170 self.order, self.preference, self.flags,
1171 self.service, self.regexp, self.replacement))
1175 class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
1177 Map from a domain name to the name of an AFS cell database server.
1179 @type subtype: C{int}
1180 @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
1181 Volume Location Server for the named AFS cell. In the case of subtype
1182 2, the host has an authenticated name server holding the cell-root
1183 directory node for the named DCE/NCA cell.
1185 @type hostname: L{Name}
1186 @ivar hostname: The domain name of a host that has a server for the cell
1187 named by this record.
1190 @ivar ttl: The maximum number of seconds which this record should be
1193 @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1195 implements(IEncodable, IRecord)
1198 fancybasename = 'AFSDB'
1199 compareAttributes = ('subtype', 'hostname', 'ttl')
1200 showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
1202 def __init__(self, subtype=0, hostname='', ttl=None):
1203 self.subtype = int(subtype)
1204 self.hostname = Name(hostname)
1205 self.ttl = str2time(ttl)
1208 def encode(self, strio, compDict = None):
1209 strio.write(struct.pack('!H', self.subtype))
1210 self.hostname.encode(strio, compDict)
1213 def decode(self, strio, length = None):
1214 r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
1216 self.hostname.decode(strio)
1220 return hash((self.subtype, self.hostname))
1224 class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
1226 The responsible person for a domain.
1229 @ivar mbox: A domain name that specifies the mailbox for the responsible
1233 @ivar txt: A domain name for which TXT RR's exist (indirection through
1234 which allows information sharing about the contents of this RP record).
1237 @ivar ttl: The maximum number of seconds which this record should be
1240 @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1242 implements(IEncodable, IRecord)
1245 fancybasename = 'RP'
1246 compareAttributes = ('mbox', 'txt', 'ttl')
1247 showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
1249 def __init__(self, mbox='', txt='', ttl=None):
1250 self.mbox = Name(mbox)
1251 self.txt = Name(txt)
1252 self.ttl = str2time(ttl)
1255 def encode(self, strio, compDict = None):
1256 self.mbox.encode(strio, compDict)
1257 self.txt.encode(strio, compDict)
1260 def decode(self, strio, length = None):
1263 self.mbox.decode(strio)
1264 self.txt.decode(strio)
1268 return hash((self.mbox, self.txt))
1272 class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
1277 @ivar cpu: Specifies the CPU type.
1280 @ivar os: Specifies the OS.
1283 @ivar ttl: The maximum number of seconds which this record should be
1286 implements(IEncodable, IRecord)
1289 fancybasename = 'HINFO'
1290 showAttributes = compareAttributes = ('cpu', 'os', 'ttl')
1292 def __init__(self, cpu='', os='', ttl=None):
1293 self.cpu, self.os = cpu, os
1294 self.ttl = str2time(ttl)
1297 def encode(self, strio, compDict = None):
1298 strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
1299 strio.write(struct.pack('!B', len(self.os)) + self.os)
1302 def decode(self, strio, length = None):
1303 cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
1304 self.cpu = readPrecisely(strio, cpu)
1305 os = struct.unpack('!B', readPrecisely(strio, 1))[0]
1306 self.os = readPrecisely(strio, os)
1309 def __eq__(self, other):
1310 if isinstance(other, Record_HINFO):
1311 return (self.os.lower() == other.os.lower() and
1312 self.cpu.lower() == other.cpu.lower() and
1313 self.ttl == other.ttl)
1314 return NotImplemented
1318 return hash((self.os.lower(), self.cpu.lower()))
1322 class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
1324 Mailbox or mail list information.
1326 This is an experimental record type.
1328 @type rmailbx: L{Name}
1329 @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
1330 for the mailing list or mailbox. If this domain name names the root,
1331 the owner of the MINFO RR is responsible for itself.
1333 @type emailbx: L{Name}
1334 @ivar emailbx: A domain-name which specifies a mailbox which is to receive
1335 error messages related to the mailing list or mailbox specified by the
1336 owner of the MINFO record. If this domain name names the root, errors
1337 should be returned to the sender of the message.
1340 @ivar ttl: The maximum number of seconds which this record should be
1343 implements(IEncodable, IRecord)
1349 fancybasename = 'MINFO'
1350 compareAttributes = ('rmailbx', 'emailbx', 'ttl')
1351 showAttributes = (('rmailbx', 'responsibility', '%s'),
1352 ('emailbx', 'errors', '%s'),
1355 def __init__(self, rmailbx='', emailbx='', ttl=None):
1356 self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
1357 self.ttl = str2time(ttl)
1360 def encode(self, strio, compDict = None):
1361 self.rmailbx.encode(strio, compDict)
1362 self.emailbx.encode(strio, compDict)
1365 def decode(self, strio, length = None):
1366 self.rmailbx, self.emailbx = Name(), Name()
1367 self.rmailbx.decode(strio)
1368 self.emailbx.decode(strio)
1372 return hash((self.rmailbx, self.emailbx))
1376 class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
1380 @type preference: C{int}
1381 @ivar preference: Specifies the preference given to this RR among others at
1382 the same owner. Lower values are preferred.
1385 @ivar name: A domain-name which specifies a host willing to act as a mail
1389 @ivar ttl: The maximum number of seconds which this record should be
1392 implements(IEncodable, IRecord)
1395 fancybasename = 'MX'
1396 compareAttributes = ('preference', 'name', 'ttl')
1397 showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
1399 def __init__(self, preference=0, name='', ttl=None, **kwargs):
1400 self.preference, self.name = int(preference), Name(kwargs.get('exchange', name))
1401 self.ttl = str2time(ttl)
1403 def encode(self, strio, compDict = None):
1404 strio.write(struct.pack('!H', self.preference))
1405 self.name.encode(strio, compDict)
1408 def decode(self, strio, length = None):
1409 self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
1411 self.name.decode(strio)
1414 return hash((self.preference, self.name))
1418 # Oh god, Record_TXT how I hate thee.
1419 class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
1423 @type data: C{list} of C{str}
1424 @ivar data: Freeform text which makes up this record.
1427 @ivar ttl: The maximum number of seconds which this record should be cached.
1429 implements(IEncodable, IRecord)
1433 fancybasename = 'TXT'
1434 showAttributes = compareAttributes = ('data', 'ttl')
1436 def __init__(self, *data, **kw):
1437 self.data = list(data)
1438 # arg man python sucks so bad
1439 self.ttl = str2time(kw.get('ttl', None))
1442 def encode(self, strio, compDict = None):
1444 strio.write(struct.pack('!B', len(d)) + d)
1447 def decode(self, strio, length = None):
1450 while soFar < length:
1451 L = struct.unpack('!B', readPrecisely(strio, 1))[0]
1452 self.data.append(readPrecisely(strio, L))
1456 "Decoded %d bytes in %s record, but rdlength is %d" % (
1457 soFar, self.fancybasename, length
1463 return hash(tuple(self.data))
1467 # This is a fallback record
1468 class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin, object):
1470 Encapsulate the wire data for unkown record types so that they can
1471 pass through the system unchanged.
1474 @ivar data: Wire data which makes up this record.
1477 @ivar ttl: The maximum number of seconds which this record should be cached.
1481 implements(IEncodable, IRecord)
1483 fancybasename = 'UNKNOWN'
1484 compareAttributes = ('data', 'ttl')
1485 showAttributes = ('data', 'ttl')
1487 def __init__(self, data='', ttl=None):
1489 self.ttl = str2time(ttl)
1492 def encode(self, strio, compDict=None):
1494 Write the raw bytes corresponding to this record's payload to the
1497 strio.write(self.data)
1500 def decode(self, strio, length=None):
1502 Load the bytes which are part of this record from the stream and store
1503 them unparsed and unmodified.
1506 raise Exception('must know length for unknown record types')
1507 self.data = readPrecisely(strio, length)
1511 return hash((self.data, self.ttl))
1515 class Record_SPF(Record_TXT):
1517 Structurally, freeform text. Semantically, a policy definition, formatted
1518 as defined in U{rfc 4408<http://www.faqs.org/rfcs/rfc4408.html>}.
1520 @type data: C{list} of C{str}
1521 @ivar data: Freeform text which makes up this record.
1524 @ivar ttl: The maximum number of seconds which this record should be cached.
1527 fancybasename = 'SPF'
1533 L{Message} contains all the information represented by a single
1534 DNS request or response.
1536 headerFmt = "!H2B4H"
1537 headerSize = struct.calcsize(headerFmt)
1539 # Question, answer, additional, and nameserver lists
1540 queries = answers = add = ns = None
1542 def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
1543 auth=0, rCode=OK, trunc=0, maxSize=512):
1544 self.maxSize = maxSize
1546 self.answer = answer
1547 self.opCode = opCode
1550 self.recDes = recDes
1556 self.additional = []
1559 def addQuery(self, name, type=ALL_RECORDS, cls=IN):
1561 Add another query to this Message.
1564 @param name: The name to query.
1567 @param type: Query type
1570 @param cls: Query class
1572 self.queries.append(Query(name, type, cls))
1575 def encode(self, strio):
1577 body_tmp = StringIO.StringIO()
1578 for q in self.queries:
1579 q.encode(body_tmp, compDict)
1580 for q in self.answers:
1581 q.encode(body_tmp, compDict)
1582 for q in self.authority:
1583 q.encode(body_tmp, compDict)
1584 for q in self.additional:
1585 q.encode(body_tmp, compDict)
1586 body = body_tmp.getvalue()
1587 size = len(body) + self.headerSize
1588 if self.maxSize and size > self.maxSize:
1590 body = body[:self.maxSize - self.headerSize]
1591 byte3 = (( ( self.answer & 1 ) << 7 )
1592 | ((self.opCode & 0xf ) << 3 )
1593 | ((self.auth & 1 ) << 2 )
1594 | ((self.trunc & 1 ) << 1 )
1595 | ( self.recDes & 1 ) )
1596 byte4 = ( ( (self.recAv & 1 ) << 7 )
1597 | (self.rCode & 0xf ) )
1599 strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
1600 len(self.queries), len(self.answers),
1601 len(self.authority), len(self.additional)))
1605 def decode(self, strio, length=None):
1607 header = readPrecisely(strio, self.headerSize)
1608 r = struct.unpack(self.headerFmt, header)
1609 self.id, byte3, byte4, nqueries, nans, nns, nadd = r
1610 self.answer = ( byte3 >> 7 ) & 1
1611 self.opCode = ( byte3 >> 3 ) & 0xf
1612 self.auth = ( byte3 >> 2 ) & 1
1613 self.trunc = ( byte3 >> 1 ) & 1
1614 self.recDes = byte3 & 1
1615 self.recAv = ( byte4 >> 7 ) & 1
1616 self.rCode = byte4 & 0xf
1619 for i in range(nqueries):
1625 self.queries.append(q)
1627 items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
1628 for (l, n) in items:
1629 self.parseRecords(l, n, strio)
1632 def parseRecords(self, list, num, strio):
1633 for i in range(num):
1634 header = RRHeader(auth=self.auth)
1636 header.decode(strio)
1639 t = self.lookupRecordType(header.type)
1642 header.payload = t(ttl=header.ttl)
1644 header.payload.decode(strio, header.rdlength)
1650 # Create a mapping from record types to their corresponding Record_*
1651 # classes. This relies on the global state which has been created so
1652 # far in initializing this module (so don't define Record classes after
1655 for name in globals():
1656 if name.startswith('Record_'):
1657 _recordTypes[globals()[name].TYPE] = globals()[name]
1659 # Clear the iteration variable out of the class namespace so it
1660 # doesn't become an attribute.
1664 def lookupRecordType(self, type):
1666 Retrieve the L{IRecord} implementation for the given record type.
1668 @param type: A record type, such as L{A} or L{NS}.
1671 @return: An object which implements L{IRecord} or C{None} if none
1672 can be found for the given type.
1673 @rtype: L{types.ClassType}
1675 return self._recordTypes.get(type, UnknownRecord)
1679 strio = StringIO.StringIO()
1681 return strio.getvalue()
1684 def fromStr(self, str):
1685 strio = StringIO.StringIO(str)
1690 class DNSMixin(object):
1692 DNS protocol mixin shared by UDP and TCP implementations.
1694 @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider which will
1695 be used to issue DNS queries and manage request timeouts.
1700 def __init__(self, controller, reactor=None):
1701 self.controller = controller
1702 self.id = random.randrange(2 ** 10, 2 ** 15)
1704 from twisted.internet import reactor
1705 self._reactor = reactor
1710 Return a unique ID for queries.
1714 if id not in self.liveMessages:
1718 def callLater(self, period, func, *args):
1720 Wrapper around reactor.callLater, mainly for test purpose.
1722 return self._reactor.callLater(period, func, *args)
1725 def _query(self, queries, timeout, id, writeMessage):
1727 Send out a message with the given queries.
1729 @type queries: C{list} of C{Query} instances
1730 @param queries: The queries to transmit
1732 @type timeout: C{int} or C{float}
1733 @param timeout: How long to wait before giving up
1736 @param id: Unique key for this request
1738 @type writeMessage: C{callable}
1739 @param writeMessage: One-parameter callback which writes the message
1742 @return: a C{Deferred} which will be fired with the result of the
1743 query, or errbacked with any errors that could happen (exceptions
1744 during writing of the query, timeout errors, ...).
1746 m = Message(id, recDes=1)
1754 resultDeferred = defer.Deferred()
1755 cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
1756 self.liveMessages[id] = (resultDeferred, cancelCall)
1758 return resultDeferred
1760 def _clearFailed(self, deferred, id):
1762 Clean the Deferred after a timeout.
1765 del self.liveMessages[id]
1768 deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
1771 class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
1773 DNS protocol over UDP.
1777 def stopProtocol(self):
1779 Stop protocol: reset state variables.
1781 self.liveMessages = {}
1783 self.transport = None
1785 def startProtocol(self):
1787 Upon start, reset internal state.
1789 self.liveMessages = {}
1792 def writeMessage(self, message, address):
1794 Send a message holding DNS queries.
1796 @type message: L{Message}
1798 self.transport.write(message.toStr(), address)
1800 def startListening(self):
1801 self._reactor.listenUDP(0, self, maxPacketSize=512)
1803 def datagramReceived(self, data, addr):
1805 Read a datagram, extract the message in it and trigger the associated
1812 log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
1815 # Nothing should trigger this, but since we're potentially
1816 # invoking a lot of different decoding methods, we might as well
1817 # be extra cautious. Anything that triggers this is itself
1819 log.err(failure.Failure(), "Unexpected decoding error")
1822 if m.id in self.liveMessages:
1823 d, canceller = self.liveMessages[m.id]
1824 del self.liveMessages[m.id]
1826 # XXX we shouldn't need this hack of catching exception on callback()
1832 if m.id not in self.resends:
1833 self.controller.messageReceived(m, self, addr)
1836 def removeResend(self, id):
1838 Mark message ID as no longer having duplication suppression.
1841 del self.resends[id]
1845 def query(self, address, queries, timeout=10, id=None):
1847 Send out a message with the given queries.
1849 @type address: C{tuple} of C{str} and C{int}
1850 @param address: The address to which to send the query
1852 @type queries: C{list} of C{Query} instances
1853 @param queries: The queries to transmit
1857 if not self.transport:
1858 # XXX transport might not get created automatically, use callLater?
1860 self.startListening()
1861 except CannotListenError:
1867 self.resends[id] = 1
1869 def writeMessage(m):
1870 self.writeMessage(m, address)
1872 return self._query(queries, timeout, id, writeMessage)
1875 class DNSProtocol(DNSMixin, protocol.Protocol):
1877 DNS protocol over TCP.
1882 def writeMessage(self, message):
1884 Send a message holding DNS queries.
1886 @type message: L{Message}
1889 self.transport.write(struct.pack('!H', len(s)) + s)
1891 def connectionMade(self):
1893 Connection is made: reset internal state, and notify the controller.
1895 self.liveMessages = {}
1896 self.controller.connectionMade(self)
1899 def connectionLost(self, reason):
1901 Notify the controller that this protocol is no longer
1904 self.controller.connectionLost(self)
1907 def dataReceived(self, data):
1911 if self.length is None and len(self.buffer) >= 2:
1912 self.length = struct.unpack('!H', self.buffer[:2])[0]
1913 self.buffer = self.buffer[2:]
1915 if len(self.buffer) >= self.length:
1916 myChunk = self.buffer[:self.length]
1921 d, canceller = self.liveMessages[m.id]
1923 self.controller.messageReceived(m, self)
1925 del self.liveMessages[m.id]
1927 # XXX we shouldn't need this hack
1933 self.buffer = self.buffer[self.length:]
1939 def query(self, queries, timeout=60):
1941 Send out a message with the given queries.
1943 @type queries: C{list} of C{Query} instances
1944 @param queries: The queries to transmit
1949 return self._query(queries, timeout, id, self.writeMessage)