1
# -*- test-case-name: twisted.names.test.test_dns -*-
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
DNS protocol implementation.
10
- Get rid of some toplevels, maybe.
11
- Put in a better lookupRecordType implementation.
20
import struct, random, types, socket
23
import cStringIO as StringIO
27
AF_INET6 = socket.AF_INET6
29
from zope.interface import implements, Interface, Attribute
33
from twisted.internet import protocol, defer
34
from twisted.internet.error import CannotListenError
35
from twisted.python import log, failure
36
from twisted.python import util as tputil
37
from twisted.python import randbytes
42
Wrapper around L{randbytes.secureRandom} to return 2 random chars.
44
return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
49
(A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
50
RP, AFSDB) = range(1, 19)
77
# 19 through 27? Eh, I'll get to 'em.
86
IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
88
# "Extended" queries (Hey, half of these are deprecated, good job)
94
ALL_RECORDS: 'ALL_RECORDS'
98
(v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
101
IN, CS, CH, HS = range(1, 5)
112
(v, k) for (k, v) in QUERY_CLASSES.items()
117
OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
118
OP_NOTIFY = 4 # RFC 1996
119
OP_UPDATE = 5 # RFC 2136
123
OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
125
class IRecord(Interface):
127
An single entry in a zone of authority.
130
TYPE = Attribute("An indicator of what kind of record this is.")
133
# Backwards compatibility aliases - these should be deprecated or something I
135
from twisted.names.error import DomainError, AuthoritativeDomainError
136
from twisted.names.error import DNSQueryTimeoutError
141
('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
142
('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
144
if isinstance(s, types.StringType):
145
s = s.upper().strip()
146
for (suff, mult) in suffixes:
148
return int(float(s[:-1]) * mult)
152
raise ValueError, "Invalid time interval specifier: " + s
156
def readPrecisely(file, l):
163
class IEncodable(Interface):
165
Interface for something which can be encoded to and decoded
169
def encode(strio, compDict = None):
171
Write a representation of this object to the given
174
@type strio: File-like object
175
@param strio: The stream to which to write bytes
177
@type compDict: C{dict} or C{None}
178
@param compDict: A dictionary of backreference addresses that have
179
have already been written to this stream and that may be used for
183
def decode(strio, length = None):
185
Reconstruct an object from data read from the given
188
@type strio: File-like object
189
@param strio: The stream from which bytes may be read
191
@type length: C{int} or C{None}
192
@param length: The number of bytes in this RDATA field. Most
193
implementations can ignore this value. Only in the case of
194
records similar to TXT where the total length is in no way
195
encoded in the data is it necessary.
200
class Charstr(object):
201
implements(IEncodable)
203
def __init__(self, string=''):
204
if not isinstance(string, str):
205
raise ValueError("%r is not a string" % (string,))
209
def encode(self, strio, compDict=None):
211
Encode this Character string into the appropriate byte format.
214
@param strio: The byte representation of this Charstr will be written
219
strio.write(chr(ind))
223
def decode(self, strio, length=None):
225
Decode a byte string into this Name.
228
@param strio: Bytes will be read from this file until the full string
231
@raise EOFError: Raised when there are not enough bytes available from
235
l = ord(readPrecisely(strio, 1))
236
self.string = readPrecisely(strio, l)
239
def __eq__(self, other):
240
if isinstance(other, Charstr):
241
return self.string == other.string
246
return hash(self.string)
255
implements(IEncodable)
257
def __init__(self, name=''):
258
assert isinstance(name, types.StringTypes), "%r is not a string" % (name,)
261
def encode(self, strio, compDict=None):
263
Encode this Name into the appropriate byte format.
266
@param strio: The byte representation of this Name will be written to
270
@param compDict: dictionary of Names that have already been encoded
271
and whose addresses may be backreferenced by this Name (for the purpose
272
of reducing the message size).
276
if compDict is not None:
279
struct.pack("!H", 0xc000 | compDict[name]))
282
compDict[name] = strio.tell() + Message.headerSize
285
label, name = name[:ind], name[ind + 1:]
287
label, name = name, ''
289
strio.write(chr(ind))
294
def decode(self, strio, length=None):
296
Decode a byte string into this Name.
299
@param strio: Bytes will be read from this file until the full Name
302
@raise EOFError: Raised when there are not enough bytes available
308
l = ord(readPrecisely(strio, 1))
314
new_off = ((l&63) << 8
315
| ord(readPrecisely(strio, 1)))
320
label = readPrecisely(strio, l)
324
self.name = self.name + '.' + label
326
def __eq__(self, other):
327
if isinstance(other, Name):
328
return str(self) == str(other)
333
return hash(str(self))
341
Represent a single DNS query.
343
@ivar name: The name about which this query is requesting information.
344
@ivar type: The query type.
345
@ivar cls: The query class.
348
implements(IEncodable)
354
def __init__(self, name='', type=A, cls=IN):
357
@param name: The name about which to request information.
360
@param type: The query type.
363
@param cls: The query class.
365
self.name = Name(name)
370
def encode(self, strio, compDict=None):
371
self.name.encode(strio, compDict)
372
strio.write(struct.pack("!HH", self.type, self.cls))
375
def decode(self, strio, length = None):
376
self.name.decode(strio)
377
buff = readPrecisely(strio, 4)
378
self.type, self.cls = struct.unpack("!HH", buff)
382
return hash((str(self.name).lower(), self.type, self.cls))
385
def __cmp__(self, other):
386
return isinstance(other, Query) and cmp(
387
(str(self.name).lower(), self.type, self.cls),
388
(str(other.name).lower(), other.type, other.cls)
389
) or cmp(self.__class__, other.__class__)
393
t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
394
c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
395
return '<Query %s %s %s>' % (self.name, t, c)
399
return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
402
class RRHeader(tputil.FancyEqMixin):
404
A resource record header.
406
@cvar fmt: C{str} specifying the byte format of an RR.
408
@ivar name: The name about which this reply contains information.
409
@ivar type: The query type of the original request.
410
@ivar cls: The query class of the original request.
411
@ivar ttl: The time-to-live for this record.
412
@ivar payload: An object that implements the IEncodable interface
413
@ivar auth: Whether this header is authoritative or not.
416
implements(IEncodable)
418
compareAttributes = ('name', 'type', 'cls', 'ttl', 'payload', 'auth')
429
cachedResponse = None
431
def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False):
434
@param name: The name about which this reply contains information.
437
@param type: The query type.
440
@param cls: The query class.
443
@param ttl: Time to live for this record.
445
@type payload: An object implementing C{IEncodable}
446
@param payload: A Query Type specific data object.
448
assert (payload is None) or (payload.TYPE == type)
450
self.name = Name(name)
454
self.payload = payload
458
def encode(self, strio, compDict=None):
459
self.name.encode(strio, compDict)
460
strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
462
prefix = strio.tell()
463
self.payload.encode(strio, compDict)
465
strio.seek(prefix - 2, 0)
466
strio.write(struct.pack('!H', aft - prefix))
470
def decode(self, strio, length = None):
471
self.name.decode(strio)
472
l = struct.calcsize(self.fmt)
473
buff = readPrecisely(strio, l)
474
r = struct.unpack(self.fmt, buff)
475
self.type, self.cls, self.ttl, self.rdlength = r
478
def isAuthoritative(self):
483
t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
484
c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
485
return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
492
class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
494
A Resource Record which consists of a single RFC 1035 domain-name.
497
@ivar name: The name associated with this record.
500
@ivar ttl: The maximum number of seconds which this record should be
503
implements(IEncodable, IRecord)
505
showAttributes = (('name', 'name', '%s'), 'ttl')
506
compareAttributes = ('name', 'ttl')
511
def __init__(self, name='', ttl=None):
512
self.name = Name(name)
513
self.ttl = str2time(ttl)
516
def encode(self, strio, compDict = None):
517
self.name.encode(strio, compDict)
520
def decode(self, strio, length = None):
522
self.name.decode(strio)
526
return hash(self.name)
529
# Kinds of RRs - oh my!
530
class Record_NS(SimpleRecord):
532
An authoritative nameserver.
539
class Record_MD(SimpleRecord):
543
This record type is obsolete.
552
class Record_MF(SimpleRecord):
556
This record type is obsolete.
565
class Record_CNAME(SimpleRecord):
567
The canonical name for an alias.
570
fancybasename = 'CNAME'
574
class Record_MB(SimpleRecord):
576
A mailbox domain name.
578
This is an experimental record type.
585
class Record_MG(SimpleRecord):
589
This is an experimental record type.
596
class Record_MR(SimpleRecord):
598
A mail rename domain name.
600
This is an experimental record type.
607
class Record_PTR(SimpleRecord):
609
A domain name pointer.
612
fancybasename = 'PTR'
616
class Record_DNAME(SimpleRecord):
618
A non-terminal DNS name redirection.
620
This record type provides the capability to map an entire subtree of the
621
DNS name space to another domain. It differs from the CNAME record which
622
maps a single node of the name space.
624
@see: U{http://www.faqs.org/rfcs/rfc2672.html}
625
@see: U{http://www.faqs.org/rfcs/rfc3363.html}
628
fancybasename = 'DNAME'
632
class Record_A(tputil.FancyEqMixin):
634
An IPv4 host address.
636
@type address: C{str}
637
@ivar address: The packed network-order representation of the IPv4 address
638
associated with this record.
641
@ivar ttl: The maximum number of seconds which this record should be
644
implements(IEncodable, IRecord)
646
compareAttributes = ('address', 'ttl')
651
def __init__(self, address='0.0.0.0', ttl=None):
652
address = socket.inet_aton(address)
653
self.address = address
654
self.ttl = str2time(ttl)
657
def encode(self, strio, compDict = None):
658
strio.write(self.address)
661
def decode(self, strio, length = None):
662
self.address = readPrecisely(strio, 4)
666
return hash(self.address)
670
return '<A address=%s ttl=%s>' % (self.dottedQuad(), self.ttl)
674
def dottedQuad(self):
675
return socket.inet_ntoa(self.address)
679
class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
681
Marks the start of a zone of authority.
683
This record describes parameters which are shared by all records within a
687
@ivar mname: The domain-name of the name server that was the original or
688
primary source of data for this zone.
691
@ivar rname: A domain-name which specifies the mailbox of the person
692
responsible for this zone.
695
@ivar serial: The unsigned 32 bit version number of the original copy of
696
the zone. Zone transfers preserve this value. This value wraps and
697
should be compared using sequence space arithmetic.
699
@type refresh: C{int}
700
@ivar refresh: A 32 bit time interval before the zone should be refreshed.
702
@type minimum: C{int}
703
@ivar minimum: The unsigned 32 bit minimum TTL field that should be
704
exported with any RR from this zone.
707
@ivar expire: A 32 bit time value that specifies the upper limit on the
708
time interval that can elapse before the zone is no longer
712
@ivar retry: A 32 bit time interval that should elapse before a failed
713
refresh should be retried.
716
@ivar ttl: The default TTL to use for records served from this zone.
718
implements(IEncodable, IRecord)
720
fancybasename = 'SOA'
721
compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'minimum', 'ttl')
722
showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
726
def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=0, minimum=0, ttl=None):
727
self.mname, self.rname = Name(mname), Name(rname)
728
self.serial, self.refresh = str2time(serial), str2time(refresh)
729
self.minimum, self.expire = str2time(minimum), str2time(expire)
730
self.retry = str2time(retry)
731
self.ttl = str2time(ttl)
734
def encode(self, strio, compDict = None):
735
self.mname.encode(strio, compDict)
736
self.rname.encode(strio, compDict)
740
self.serial, self.refresh, self.retry, self.expire,
746
def decode(self, strio, length = None):
747
self.mname, self.rname = Name(), Name()
748
self.mname.decode(strio)
749
self.rname.decode(strio)
750
r = struct.unpack('!LlllL', readPrecisely(strio, 20))
751
self.serial, self.refresh, self.retry, self.expire, self.minimum = r
756
self.serial, self.mname, self.rname,
757
self.refresh, self.expire, self.retry
762
class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
766
This is an experimental record type.
769
@ivar ttl: The maximum number of seconds which this record should be
772
implements(IEncodable, IRecord)
774
fancybasename = 'NULL'
775
showAttributes = compareAttributes = ('payload', 'ttl')
779
def __init__(self, payload=None, ttl=None):
780
self.payload = payload
781
self.ttl = str2time(ttl)
784
def encode(self, strio, compDict = None):
785
strio.write(self.payload)
788
def decode(self, strio, length = None):
789
self.payload = readPrecisely(strio, length)
793
return hash(self.payload)
797
class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
799
A well known service description.
801
This record type is obsolete. See L{Record_SRV}.
803
@type address: C{str}
804
@ivar address: The packed network-order representation of the IPv4 address
805
associated with this record.
807
@type protocol: C{int}
808
@ivar protocol: The 8 bit IP protocol number for which this service map is
812
@ivar map: A bitvector indicating the services available at the specified
816
@ivar ttl: The maximum number of seconds which this record should be
819
implements(IEncodable, IRecord)
821
fancybasename = "WKS"
822
compareAttributes = ('address', 'protocol', 'map', 'ttl')
823
showAttributes = [('_address', 'address', '%s'), 'protocol', 'ttl']
827
_address = property(lambda self: socket.inet_ntoa(self.address))
829
def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
830
self.address = socket.inet_aton(address)
831
self.protocol, self.map = protocol, map
832
self.ttl = str2time(ttl)
835
def encode(self, strio, compDict = None):
836
strio.write(self.address)
837
strio.write(struct.pack('!B', self.protocol))
838
strio.write(self.map)
841
def decode(self, strio, length = None):
842
self.address = readPrecisely(strio, 4)
843
self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
844
self.map = readPrecisely(strio, length - 5)
848
return hash((self.address, self.protocol, self.map))
852
class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
854
An IPv6 host address.
856
@type address: C{str}
857
@ivar address: The packed network-order representation of the IPv6 address
858
associated with this record.
861
@ivar ttl: The maximum number of seconds which this record should be
864
@see: U{http://www.faqs.org/rfcs/rfc1886.html}
866
implements(IEncodable, IRecord)
869
fancybasename = 'AAAA'
870
showAttributes = (('_address', 'address', '%s'), 'ttl')
871
compareAttributes = ('address', 'ttl')
873
_address = property(lambda self: socket.inet_ntop(AF_INET6, self.address))
875
def __init__(self, address = '::', ttl=None):
876
self.address = socket.inet_pton(AF_INET6, address)
877
self.ttl = str2time(ttl)
880
def encode(self, strio, compDict = None):
881
strio.write(self.address)
884
def decode(self, strio, length = None):
885
self.address = readPrecisely(strio, 16)
889
return hash(self.address)
893
class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
897
This is an experimental record type.
899
@type prefixLen: C{int}
900
@ivar prefixLen: The length of the suffix.
903
@ivar suffix: An IPv6 address suffix in network order.
905
@type prefix: L{Name}
906
@ivar prefix: If specified, a name which will be used as a prefix for other
910
@ivar bytes: The length of the prefix.
913
@ivar ttl: The maximum number of seconds which this record should be
916
@see: U{http://www.faqs.org/rfcs/rfc2874.html}
917
@see: U{http://www.faqs.org/rfcs/rfc3363.html}
918
@see: U{http://www.faqs.org/rfcs/rfc3364.html}
920
implements(IEncodable, IRecord)
924
showAttributes = (('_suffix', 'suffix', '%s'), ('prefix', 'prefix', '%s'), 'ttl')
925
compareAttributes = ('prefixLen', 'prefix', 'suffix', 'ttl')
927
_suffix = property(lambda self: socket.inet_ntop(AF_INET6, self.suffix))
929
def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
930
self.prefixLen = prefixLen
931
self.suffix = socket.inet_pton(AF_INET6, suffix)
932
self.prefix = Name(prefix)
933
self.bytes = int((128 - self.prefixLen) / 8.0)
934
self.ttl = str2time(ttl)
937
def encode(self, strio, compDict = None):
938
strio.write(struct.pack('!B', self.prefixLen))
940
strio.write(self.suffix[-self.bytes:])
942
# This may not be compressed
943
self.prefix.encode(strio, None)
946
def decode(self, strio, length = None):
947
self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
948
self.bytes = int((128 - self.prefixLen) / 8.0)
950
self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
952
self.prefix.decode(strio)
955
def __eq__(self, other):
956
if isinstance(other, Record_A6):
957
return (self.prefixLen == other.prefixLen and
958
self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
959
self.prefix == other.prefix and
960
self.ttl == other.ttl)
961
return NotImplemented
965
return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
969
return '<A6 %s %s (%d) ttl=%s>' % (
971
socket.inet_ntop(AF_INET6, self.suffix),
972
self.prefixLen, self.ttl
977
class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
979
The location of the server(s) for a specific protocol and domain.
981
This is an experimental record type.
983
@type priority: C{int}
984
@ivar priority: The priority of this target host. A client MUST attempt to
985
contact the target host with the lowest-numbered priority it can reach;
986
target hosts with the same priority SHOULD be tried in an order defined
990
@ivar weight: Specifies a relative weight for entries with the same
991
priority. Larger weights SHOULD be given a proportionately higher
992
probability of being selected.
995
@ivar port: The port on this target host of this service.
997
@type target: L{Name}
998
@ivar target: The domain name of the target host. There MUST be one or
999
more address records for this name, the name MUST NOT be an alias (in
1000
the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
1001
required, to return the address record(s) in the Additional Data
1002
section. Unless and until permitted by future standards action, name
1003
compression is not to be used for this field.
1006
@ivar ttl: The maximum number of seconds which this record should be
1009
@see: U{http://www.faqs.org/rfcs/rfc2782.html}
1011
implements(IEncodable, IRecord)
1014
fancybasename = 'SRV'
1015
compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
1016
showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
1018
def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
1019
self.priority = int(priority)
1020
self.weight = int(weight)
1021
self.port = int(port)
1022
self.target = Name(target)
1023
self.ttl = str2time(ttl)
1026
def encode(self, strio, compDict = None):
1027
strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
1028
# This can't be compressed
1029
self.target.encode(strio, None)
1032
def decode(self, strio, length = None):
1033
r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
1034
self.priority, self.weight, self.port = r
1035
self.target = Name()
1036
self.target.decode(strio)
1040
return hash((self.priority, self.weight, self.port, self.target))
1044
class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
1046
The location of the server(s) for a specific protocol and domain.
1049
@ivar order: An integer specifying the order in which the NAPTR records
1050
MUST be processed to ensure the correct ordering of rules. Low numbers
1051
are processed before high numbers.
1053
@type preference: C{int}
1054
@ivar preference: An integer that specifies the order in which NAPTR
1055
records with equal "order" values SHOULD be processed, low numbers
1056
being processed before high numbers.
1058
@type flag: L{Charstr}
1059
@ivar flag: A <character-string> containing flags to control aspects of the
1060
rewriting and interpretation of the fields in the record. Flags
1061
aresingle characters from the set [A-Z0-9]. The case of the alphabetic
1062
characters is not significant.
1064
At this time only four flags, "S", "A", "U", and "P", are defined.
1066
@type service: L{Charstr}
1067
@ivar service: Specifies the service(s) available down this rewrite path.
1068
It may also specify the particular protocol that is used to talk with a
1069
service. A protocol MUST be specified if the flags field states that
1070
the NAPTR is terminal.
1072
@type regexp: L{Charstr}
1073
@ivar regexp: A STRING containing a substitution expression that is applied
1074
to the original string held by the client in order to construct the
1075
next domain name to lookup.
1077
@type replacement: L{Name}
1078
@ivar replacement: The next NAME to query for NAPTR, SRV, or address
1079
records depending on the value of the flags field. This MUST be a
1080
fully qualified domain-name.
1083
@ivar ttl: The maximum number of seconds which this record should be
1086
@see: U{http://www.faqs.org/rfcs/rfc2915.html}
1088
implements(IEncodable, IRecord)
1091
compareAttributes = ('order', 'preference', 'flags', 'service', 'regexp',
1093
fancybasename = 'NAPTR'
1094
showAttributes = ('order', 'preference', ('flags', 'flags', '%s'),
1095
('service', 'service', '%s'), ('regexp', 'regexp', '%s'),
1096
('replacement', 'replacement', '%s'), 'ttl')
1098
def __init__(self, order=0, preference=0, flags='', service='', regexp='',
1099
replacement='', ttl=None):
1100
self.order = int(order)
1101
self.preference = int(preference)
1102
self.flags = Charstr(flags)
1103
self.service = Charstr(service)
1104
self.regexp = Charstr(regexp)
1105
self.replacement = Name(replacement)
1106
self.ttl = str2time(ttl)
1109
def encode(self, strio, compDict=None):
1110
strio.write(struct.pack('!HH', self.order, self.preference))
1111
# This can't be compressed
1112
self.flags.encode(strio, None)
1113
self.service.encode(strio, None)
1114
self.regexp.encode(strio, None)
1115
self.replacement.encode(strio, None)
1118
def decode(self, strio, length=None):
1119
r = struct.unpack('!HH', readPrecisely(strio, struct.calcsize('!HH')))
1120
self.order, self.preference = r
1121
self.flags = Charstr()
1122
self.service = Charstr()
1123
self.regexp = Charstr()
1124
self.replacement = Name()
1125
self.flags.decode(strio)
1126
self.service.decode(strio)
1127
self.regexp.decode(strio)
1128
self.replacement.decode(strio)
1133
self.order, self.preference, self.flags,
1134
self.service, self.regexp, self.replacement))
1138
class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
1140
Map from a domain name to the name of an AFS cell database server.
1142
@type subtype: C{int}
1143
@ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
1144
Volume Location Server for the named AFS cell. In the case of subtype
1145
2, the host has an authenticated name server holding the cell-root
1146
directory node for the named DCE/NCA cell.
1148
@type hostname: L{Name}
1149
@ivar hostname: The domain name of a host that has a server for the cell
1150
named by this record.
1153
@ivar ttl: The maximum number of seconds which this record should be
1156
@see: U{http://www.faqs.org/rfcs/rfc1183.html}
1158
implements(IEncodable, IRecord)
1161
fancybasename = 'AFSDB'
1162
compareAttributes = ('subtype', 'hostname', 'ttl')
1163
showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
1165
def __init__(self, subtype=0, hostname='', ttl=None):
1166
self.subtype = int(subtype)
1167
self.hostname = Name(hostname)
1168
self.ttl = str2time(ttl)
1171
def encode(self, strio, compDict = None):
1172
strio.write(struct.pack('!H', self.subtype))
1173
self.hostname.encode(strio, compDict)
1176
def decode(self, strio, length = None):
1177
r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
1179
self.hostname.decode(strio)
1183
return hash((self.subtype, self.hostname))
1187
class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
1189
The responsible person for a domain.
1192
@ivar mbox: A domain name that specifies the mailbox for the responsible
1196
@ivar txt: A domain name for which TXT RR's exist (indirection through
1197
which allows information sharing about the contents of this RP record).
1200
@ivar ttl: The maximum number of seconds which this record should be
1203
@see: U{http://www.faqs.org/rfcs/rfc1183.html}
1205
implements(IEncodable, IRecord)
1208
fancybasename = 'RP'
1209
compareAttributes = ('mbox', 'txt', 'ttl')
1210
showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
1212
def __init__(self, mbox='', txt='', ttl=None):
1213
self.mbox = Name(mbox)
1214
self.txt = Name(txt)
1215
self.ttl = str2time(ttl)
1218
def encode(self, strio, compDict = None):
1219
self.mbox.encode(strio, compDict)
1220
self.txt.encode(strio, compDict)
1223
def decode(self, strio, length = None):
1226
self.mbox.decode(strio)
1227
self.txt.decode(strio)
1231
return hash((self.mbox, self.txt))
1235
class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
1240
@ivar cpu: Specifies the CPU type.
1243
@ivar os: Specifies the OS.
1246
@ivar ttl: The maximum number of seconds which this record should be
1249
implements(IEncodable, IRecord)
1252
fancybasename = 'HINFO'
1253
showAttributes = compareAttributes = ('cpu', 'os', 'ttl')
1255
def __init__(self, cpu='', os='', ttl=None):
1256
self.cpu, self.os = cpu, os
1257
self.ttl = str2time(ttl)
1260
def encode(self, strio, compDict = None):
1261
strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
1262
strio.write(struct.pack('!B', len(self.os)) + self.os)
1265
def decode(self, strio, length = None):
1266
cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
1267
self.cpu = readPrecisely(strio, cpu)
1268
os = struct.unpack('!B', readPrecisely(strio, 1))[0]
1269
self.os = readPrecisely(strio, os)
1272
def __eq__(self, other):
1273
if isinstance(other, Record_HINFO):
1274
return (self.os.lower() == other.os.lower() and
1275
self.cpu.lower() == other.cpu.lower() and
1276
self.ttl == other.ttl)
1277
return NotImplemented
1281
return hash((self.os.lower(), self.cpu.lower()))
1285
class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
1287
Mailbox or mail list information.
1289
This is an experimental record type.
1291
@type rmailbx: L{Name}
1292
@ivar rmailbx: A domain-name which specifies a mailbox which is responsible
1293
for the mailing list or mailbox. If this domain name names the root,
1294
the owner of the MINFO RR is responsible for itself.
1296
@type emailbx: L{Name}
1297
@ivar emailbx: A domain-name which specifies a mailbox which is to receive
1298
error messages related to the mailing list or mailbox specified by the
1299
owner of the MINFO record. If this domain name names the root, errors
1300
should be returned to the sender of the message.
1303
@ivar ttl: The maximum number of seconds which this record should be
1306
implements(IEncodable, IRecord)
1312
fancybasename = 'MINFO'
1313
compareAttributes = ('rmailbx', 'emailbx', 'ttl')
1314
showAttributes = (('rmailbx', 'responsibility', '%s'),
1315
('emailbx', 'errors', '%s'),
1318
def __init__(self, rmailbx='', emailbx='', ttl=None):
1319
self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
1320
self.ttl = str2time(ttl)
1323
def encode(self, strio, compDict = None):
1324
self.rmailbx.encode(strio, compDict)
1325
self.emailbx.encode(strio, compDict)
1328
def decode(self, strio, length = None):
1329
self.rmailbx, self.emailbx = Name(), Name()
1330
self.rmailbx.decode(strio)
1331
self.emailbx.decode(strio)
1335
return hash((self.rmailbx, self.emailbx))
1339
class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
1343
@type preference: C{int}
1344
@ivar preference: Specifies the preference given to this RR among others at
1345
the same owner. Lower values are preferred.
1348
@ivar name: A domain-name which specifies a host willing to act as a mail
1352
@ivar ttl: The maximum number of seconds which this record should be
1355
implements(IEncodable, IRecord)
1358
fancybasename = 'MX'
1359
compareAttributes = ('preference', 'name', 'ttl')
1360
showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
1362
def __init__(self, preference=0, name='', ttl=None, **kwargs):
1363
self.preference, self.name = int(preference), Name(kwargs.get('exchange', name))
1364
self.ttl = str2time(ttl)
1366
def encode(self, strio, compDict = None):
1367
strio.write(struct.pack('!H', self.preference))
1368
self.name.encode(strio, compDict)
1371
def decode(self, strio, length = None):
1372
self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
1374
self.name.decode(strio)
1377
warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklevel=2)
1380
exchange = property(exchange)
1383
return hash((self.preference, self.name))
1387
# Oh god, Record_TXT how I hate thee.
1388
class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
1392
@type data: C{list} of C{str}
1393
@ivar data: Freeform text which makes up this record.
1395
implements(IEncodable, IRecord)
1399
fancybasename = 'TXT'
1400
showAttributes = compareAttributes = ('data', 'ttl')
1402
def __init__(self, *data, **kw):
1403
self.data = list(data)
1404
# arg man python sucks so bad
1405
self.ttl = str2time(kw.get('ttl', None))
1408
def encode(self, strio, compDict = None):
1410
strio.write(struct.pack('!B', len(d)) + d)
1413
def decode(self, strio, length = None):
1416
while soFar < length:
1417
L = struct.unpack('!B', readPrecisely(strio, 1))[0]
1418
self.data.append(readPrecisely(strio, L))
1422
"Decoded %d bytes in TXT record, but rdlength is %d" % (
1429
return hash(tuple(self.data))
1434
headerFmt = "!H2B4H"
1435
headerSize = struct.calcsize(headerFmt)
1437
# Question, answer, additional, and nameserver lists
1438
queries = answers = add = ns = None
1440
def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
1441
auth=0, rCode=OK, trunc=0, maxSize=512):
1442
self.maxSize = maxSize
1444
self.answer = answer
1445
self.opCode = opCode
1448
self.recDes = recDes
1454
self.additional = []
1457
def addQuery(self, name, type=ALL_RECORDS, cls=IN):
1459
Add another query to this Message.
1462
@param name: The name to query.
1465
@param type: Query type
1468
@param cls: Query class
1470
self.queries.append(Query(name, type, cls))
1473
def encode(self, strio):
1475
body_tmp = StringIO.StringIO()
1476
for q in self.queries:
1477
q.encode(body_tmp, compDict)
1478
for q in self.answers:
1479
q.encode(body_tmp, compDict)
1480
for q in self.authority:
1481
q.encode(body_tmp, compDict)
1482
for q in self.additional:
1483
q.encode(body_tmp, compDict)
1484
body = body_tmp.getvalue()
1485
size = len(body) + self.headerSize
1486
if self.maxSize and size > self.maxSize:
1488
body = body[:self.maxSize - self.headerSize]
1489
byte3 = (( ( self.answer & 1 ) << 7 )
1490
| ((self.opCode & 0xf ) << 3 )
1491
| ((self.auth & 1 ) << 2 )
1492
| ((self.trunc & 1 ) << 1 )
1493
| ( self.recDes & 1 ) )
1494
byte4 = ( ( (self.recAv & 1 ) << 7 )
1495
| (self.rCode & 0xf ) )
1497
strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
1498
len(self.queries), len(self.answers),
1499
len(self.authority), len(self.additional)))
1503
def decode(self, strio, length=None):
1505
header = readPrecisely(strio, self.headerSize)
1506
r = struct.unpack(self.headerFmt, header)
1507
self.id, byte3, byte4, nqueries, nans, nns, nadd = r
1508
self.answer = ( byte3 >> 7 ) & 1
1509
self.opCode = ( byte3 >> 3 ) & 0xf
1510
self.auth = ( byte3 >> 2 ) & 1
1511
self.trunc = ( byte3 >> 1 ) & 1
1512
self.recDes = byte3 & 1
1513
self.recAv = ( byte4 >> 7 ) & 1
1514
self.rCode = byte4 & 0xf
1517
for i in range(nqueries):
1523
self.queries.append(q)
1525
items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
1526
for (l, n) in items:
1527
self.parseRecords(l, n, strio)
1530
def parseRecords(self, list, num, strio):
1531
for i in range(num):
1534
header.decode(strio)
1537
t = self.lookupRecordType(header.type)
1540
header.payload = t(ttl=header.ttl)
1542
header.payload.decode(strio, header.rdlength)
1548
def lookupRecordType(self, type):
1549
return globals().get('Record_' + QUERY_TYPES.get(type, ''), None)
1553
strio = StringIO.StringIO()
1555
return strio.getvalue()
1558
def fromStr(self, str):
1559
strio = StringIO.StringIO(str)
1562
class DNSMixin(object):
1564
DNS protocol mixin shared by UDP and TCP implementations.
1566
@ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider which will
1567
be used to issue DNS queries and manage request timeouts.
1572
def __init__(self, controller, reactor=None):
1573
self.controller = controller
1574
self.id = random.randrange(2 ** 10, 2 ** 15)
1576
from twisted.internet import reactor
1577
self._reactor = reactor
1582
Return a unique ID for queries.
1586
if id not in self.liveMessages:
1590
def callLater(self, period, func, *args):
1592
Wrapper around reactor.callLater, mainly for test purpose.
1594
return self._reactor.callLater(period, func, *args)
1597
def _query(self, queries, timeout, id, writeMessage):
1599
Send out a message with the given queries.
1601
@type queries: C{list} of C{Query} instances
1602
@param queries: The queries to transmit
1604
@type timeout: C{int} or C{float}
1605
@param timeout: How long to wait before giving up
1608
@param id: Unique key for this request
1610
@type writeMessage: C{callable}
1611
@param writeMessage: One-parameter callback which writes the message
1614
@return: a C{Deferred} which will be fired with the result of the
1615
query, or errbacked with any errors that could happen (exceptions
1616
during writing of the query, timeout errors, ...).
1618
m = Message(id, recDes=1)
1626
resultDeferred = defer.Deferred()
1627
cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
1628
self.liveMessages[id] = (resultDeferred, cancelCall)
1630
return resultDeferred
1632
def _clearFailed(self, deferred, id):
1634
Clean the Deferred after a timeout.
1637
del self.liveMessages[id]
1640
deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
1643
class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
1645
DNS protocol over UDP.
1649
def stopProtocol(self):
1651
Stop protocol: reset state variables.
1653
self.liveMessages = {}
1655
self.transport = None
1657
def startProtocol(self):
1659
Upon start, reset internal state.
1661
self.liveMessages = {}
1664
def writeMessage(self, message, address):
1666
Send a message holding DNS queries.
1668
@type message: L{Message}
1670
self.transport.write(message.toStr(), address)
1672
def startListening(self):
1673
self._reactor.listenUDP(0, self, maxPacketSize=512)
1675
def datagramReceived(self, data, addr):
1677
Read a datagram, extract the message in it and trigger the associated
1684
log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
1687
# Nothing should trigger this, but since we're potentially
1688
# invoking a lot of different decoding methods, we might as well
1689
# be extra cautious. Anything that triggers this is itself
1691
log.err(failure.Failure(), "Unexpected decoding error")
1694
if m.id in self.liveMessages:
1695
d, canceller = self.liveMessages[m.id]
1696
del self.liveMessages[m.id]
1698
# XXX we shouldn't need this hack of catching exception on callback()
1704
if m.id not in self.resends:
1705
self.controller.messageReceived(m, self, addr)
1708
def removeResend(self, id):
1710
Mark message ID as no longer having duplication suppression.
1713
del self.resends[id]
1717
def query(self, address, queries, timeout=10, id=None):
1719
Send out a message with the given queries.
1721
@type address: C{tuple} of C{str} and C{int}
1722
@param address: The address to which to send the query
1724
@type queries: C{list} of C{Query} instances
1725
@param queries: The queries to transmit
1729
if not self.transport:
1730
# XXX transport might not get created automatically, use callLater?
1732
self.startListening()
1733
except CannotListenError:
1739
self.resends[id] = 1
1741
def writeMessage(m):
1742
self.writeMessage(m, address)
1744
return self._query(queries, timeout, id, writeMessage)
1747
class DNSProtocol(DNSMixin, protocol.Protocol):
1749
DNS protocol over TCP.
1754
def writeMessage(self, message):
1756
Send a message holding DNS queries.
1758
@type message: L{Message}
1761
self.transport.write(struct.pack('!H', len(s)) + s)
1763
def connectionMade(self):
1765
Connection is made: reset internal state, and notify the controller.
1767
self.liveMessages = {}
1768
self.controller.connectionMade(self)
1771
def connectionLost(self, reason):
1773
Notify the controller that this protocol is no longer
1776
self.controller.connectionLost(self)
1779
def dataReceived(self, data):
1783
if self.length is None and len(self.buffer) >= 2:
1784
self.length = struct.unpack('!H', self.buffer[:2])[0]
1785
self.buffer = self.buffer[2:]
1787
if len(self.buffer) >= self.length:
1788
myChunk = self.buffer[:self.length]
1793
d, canceller = self.liveMessages[m.id]
1795
self.controller.messageReceived(m, self)
1797
del self.liveMessages[m.id]
1799
# XXX we shouldn't need this hack
1805
self.buffer = self.buffer[self.length:]
1811
def query(self, queries, timeout=60):
1813
Send out a message with the given queries.
1815
@type queries: C{list} of C{Query} instances
1816
@param queries: The queries to transmit
1821
return self._query(queries, timeout, id, self.writeMessage)