1
# -*- test-case-name: twisted.names.test.test_dns -*-
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
DNS protocol implementation.
9
API Stability: Unstable
12
- Get rid of some toplevels, maybe.
13
- Put in a better lookupRecordType implementation.
15
@author: U{Moshe Zadka<mailto:moshez@twistedmatrix.com>},
16
U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
22
import struct, random, types, socket
25
import cStringIO as StringIO
29
AF_INET6 = socket.AF_INET6
32
from Crypto.Util import randpool
34
for randSource in ('urandom',):
36
f = file('/dev/' + randSource)
42
def randomSource(r = file('/dev/' + randSource, 'rb').read):
43
return struct.unpack('H', r(2))[0]
47
"PyCrypto not available - proceeding with non-cryptographically "
48
"secure random source",
54
return random.randint(0, 65535)
56
def randomSource(r = randpool.RandomPool().get_bytes):
57
return struct.unpack('H', r(2))[0]
58
from zope.interface import implements, Interface
62
from twisted.internet import protocol, defer
63
from twisted.python import log, failure
64
from twisted.python import util as tputil
68
(A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
69
RP, AFSDB) = range(1, 19)
95
# 19 through 27? Eh, I'll get to 'em.
104
IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
106
# "Extended" queries (Hey, half of these are deprecated, good job)
112
ALL_RECORDS: 'ALL_RECORDS'
116
(v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
119
IN, CS, CH, HS = range(1, 5)
130
(v, k) for (k, v) in QUERY_CLASSES.items()
135
OP_QUERY, OP_INVERSE, OP_STATUS, OP_NOTIFY = range(4)
138
OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
140
class IRecord(Interface):
141
"""An single entry in a zone of authority.
143
@cvar TYPE: An indicator of what kind of record this is.
147
# Backwards compatibility aliases - these should be deprecated or something I
149
from twisted.names.error import DomainError, AuthoritativeDomainError
150
from twisted.names.error import DNSQueryTimeoutError
155
('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
156
('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
158
if isinstance(s, types.StringType):
159
s = s.upper().strip()
160
for (suff, mult) in suffixes:
162
return int(float(s[:-1]) * mult)
166
raise ValueError, "Invalid time interval specifier: " + s
170
def readPrecisely(file, l):
177
class IEncodable(Interface):
179
Interface for something which can be encoded to and decoded
182
def encode(strio, compDict = None):
184
Write a representation of this object to the given
187
@type strio: File-like object
188
@param strio: The stream to which to write bytes
190
@type compDict: C{dict} or C{None}
191
@param compDict: A dictionary of backreference addresses that have
192
have already been written to this stream and that may be used for
196
def decode(strio, length = None):
198
Reconstruct an object from data read from the given
201
@type strio: File-like object
202
@param strio: The stream from which bytes may be read
204
@type length: C{int} or C{None}
205
@param length: The number of bytes in this RDATA field. Most
206
implementations can ignore this value. Only in the case of
207
records similar to TXT where the total length is in no way
208
encoded in the data is it necessary.
213
implements(IEncodable)
215
def __init__(self, name=''):
216
assert isinstance(name, types.StringTypes), "%r is not a string" % (name,)
219
def encode(self, strio, compDict=None):
221
Encode this Name into the appropriate byte format.
224
@param strio: The byte representation of this Name will be written to
228
@param compDict: dictionary of Names that have already been encoded
229
and whose addresses may be backreferenced by this Name (for the purpose
230
of reducing the message size).
234
if compDict is not None:
235
if compDict.has_key(name):
237
struct.pack("!H", 0xc000 | compDict[name]))
240
compDict[name] = strio.tell() + Message.headerSize
243
label, name = name[:ind], name[ind + 1:]
245
label, name = name, ''
247
strio.write(chr(ind))
252
def decode(self, strio, length = None):
254
Decode a byte string into this Name.
257
@param strio: Bytes will be read from this file until the full Name
260
@raise EOFError: Raised when there are not enough bytes available
266
l = ord(readPrecisely(strio, 1))
272
new_off = ((l&63) << 8
273
| ord(readPrecisely(strio, 1)))
278
label = readPrecisely(strio, l)
282
self.name = self.name + '.' + label
284
def __eq__(self, other):
285
if isinstance(other, Name):
286
return str(self) == str(other)
291
return hash(str(self))
299
Represent a single DNS query.
301
@ivar name: The name about which this query is requesting information.
302
@ivar type: The query type.
303
@ivar cls: The query class.
306
implements(IEncodable)
312
def __init__(self, name='', type=A, cls=IN):
315
@param name: The name about which to request information.
318
@param type: The query type.
321
@param cls: The query class.
323
self.name = Name(name)
328
def encode(self, strio, compDict=None):
329
self.name.encode(strio, compDict)
330
strio.write(struct.pack("!HH", self.type, self.cls))
333
def decode(self, strio, length = None):
334
self.name.decode(strio)
335
buff = readPrecisely(strio, 4)
336
self.type, self.cls = struct.unpack("!HH", buff)
340
return hash((str(self.name).lower(), self.type, self.cls))
343
def __cmp__(self, other):
344
return isinstance(other, Query) and cmp(
345
(str(self.name).lower(), self.type, self.cls),
346
(str(other.name).lower(), other.type, other.cls)
347
) or cmp(self.__class__, other.__class__)
351
t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
352
c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
353
return '<Query %s %s %s>' % (self.name, t, c)
357
return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
362
A resource record header.
364
@cvar fmt: C{str} specifying the byte format of an RR.
366
@ivar name: The name about which this reply contains information.
367
@ivar type: The query type of the original request.
368
@ivar cls: The query class of the original request.
369
@ivar ttl: The time-to-live for this record.
370
@ivar payload: An object that implements the IEncodable interface
371
@ivar auth: Whether this header is authoritative or not.
374
implements(IEncodable)
385
cachedResponse = None
387
def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False):
390
@param name: The name about which this reply contains information.
393
@param type: The query type.
396
@param cls: The query class.
399
@param ttl: Time to live for this record.
401
@type payload: An object implementing C{IEncodable}
402
@param payload: A Query Type specific data object.
404
assert (payload is None) or (payload.TYPE == type)
406
self.name = Name(name)
410
self.payload = payload
414
def encode(self, strio, compDict=None):
415
self.name.encode(strio, compDict)
416
strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
418
prefix = strio.tell()
419
self.payload.encode(strio, compDict)
421
strio.seek(prefix - 2, 0)
422
strio.write(struct.pack('!H', aft - prefix))
426
def decode(self, strio, length = None):
427
self.name.decode(strio)
428
l = struct.calcsize(self.fmt)
429
buff = readPrecisely(strio, l)
430
r = struct.unpack(self.fmt, buff)
431
self.type, self.cls, self.ttl, self.rdlength = r
434
def isAuthoritative(self):
439
t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
440
c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
441
return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
446
class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
448
A Resource Record which consists of a single RFC 1035 domain-name.
452
implements(IEncodable, IRecord)
455
showAttributes = (('name', 'name', '%s'), 'ttl')
456
compareAttributes = ('name', 'ttl')
458
def __init__(self, name='', ttl=None):
459
self.name = Name(name)
460
self.ttl = str2time(ttl)
463
def encode(self, strio, compDict = None):
464
self.name.encode(strio, compDict)
467
def decode(self, strio, length = None):
469
self.name.decode(strio)
473
return hash(self.name)
476
# Kinds of RRs - oh my!
477
class Record_NS(SimpleRecord):
480
class Record_MD(SimpleRecord): # OBSOLETE
483
class Record_MF(SimpleRecord): # OBSOLETE
486
class Record_CNAME(SimpleRecord):
489
class Record_MB(SimpleRecord): # EXPERIMENTAL
492
class Record_MG(SimpleRecord): # EXPERIMENTAL
495
class Record_MR(SimpleRecord): # EXPERIMENTAL
498
class Record_PTR(SimpleRecord):
501
class Record_DNAME(SimpleRecord):
504
class Record_A(tputil.FancyEqMixin):
505
implements(IEncodable, IRecord)
510
compareAttributes = ('address', 'ttl')
512
def __init__(self, address='0.0.0.0', ttl=None):
513
address = socket.inet_aton(address)
514
self.address = address
515
self.ttl = str2time(ttl)
518
def encode(self, strio, compDict = None):
519
strio.write(self.address)
522
def decode(self, strio, length = None):
523
self.address = readPrecisely(strio, 4)
527
return hash(self.address)
531
return '<A %s ttl=%s>' % (self.dottedQuad(), self.ttl)
534
def dottedQuad(self):
535
return socket.inet_ntoa(self.address)
538
class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
539
implements(IEncodable, IRecord)
541
compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'ttl')
542
showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
546
def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=0, minimum=0, ttl=None):
547
self.mname, self.rname = Name(mname), Name(rname)
548
self.serial, self.refresh = str2time(serial), str2time(refresh)
549
self.minimum, self.expire = str2time(minimum), str2time(expire)
550
self.retry = str2time(retry)
551
self.ttl = str2time(ttl)
554
def encode(self, strio, compDict = None):
555
self.mname.encode(strio, compDict)
556
self.rname.encode(strio, compDict)
560
self.serial, self.refresh, self.retry, self.expire,
566
def decode(self, strio, length = None):
567
self.mname, self.rname = Name(), Name()
568
self.mname.decode(strio)
569
self.rname.decode(strio)
570
r = struct.unpack('!LlllL', readPrecisely(strio, 20))
571
self.serial, self.refresh, self.retry, self.expire, self.minimum = r
576
self.serial, self.mname, self.rname,
577
self.refresh, self.expire, self.retry
581
class Record_NULL: # EXPERIMENTAL
582
implements(IEncodable, IRecord)
585
def __init__(self, payload=None, ttl=None):
586
self.payload = payload
587
self.ttl = str2time(ttl)
590
def encode(self, strio, compDict = None):
591
strio.write(self.payload)
594
def decode(self, strio, length = None):
595
self.payload = readPrecisely(strio, length)
599
return hash(self.payload)
602
class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin): # OBSOLETE
603
implements(IEncodable, IRecord)
606
compareAttributes = ('address', 'protocol', 'map', 'ttl')
607
showAttributes = ('address', 'protocol', 'ttl')
609
def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
610
self.address = socket.inet_aton(address)
611
self.protocol, self.map = protocol, map
612
self.ttl = str2time(ttl)
615
def encode(self, strio, compDict = None):
616
strio.write(self.address)
617
strio.write(struct.pack('!B', self.protocol))
618
strio.write(self.map)
621
def decode(self, strio, length = None):
622
self.address = readPrecisely(strio, 4)
623
self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
624
self.map = readPrecisely(strio, length - 5)
628
return hash((self.address, self.protocol, self.map))
631
class Record_AAAA(tputil.FancyEqMixin): # OBSOLETE (or headed there)
632
implements(IEncodable, IRecord)
635
compareAttributes = ('address', 'ttl')
637
def __init__(self, address = '::', ttl=None):
638
self.address = socket.inet_pton(AF_INET6, address)
639
self.ttl = str2time(ttl)
642
def encode(self, strio, compDict = None):
643
strio.write(self.address)
646
def decode(self, strio, length = None):
647
self.address = readPrecisely(strio, 16)
651
return hash(self.address)
655
return '<AAAA %s ttl=%s>' % (socket.inet_ntop(AF_INET6, self.address), self.ttl)
659
implements(IEncodable, IRecord)
662
def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
663
self.prefixLen = prefixLen
664
self.suffix = socket.inet_pton(AF_INET6, suffix)
665
self.prefix = Name(prefix)
666
self.bytes = int((128 - self.prefixLen) / 8.0)
667
self.ttl = str2time(ttl)
670
def encode(self, strio, compDict = None):
671
strio.write(struct.pack('!B', self.prefixLen))
673
strio.write(self.suffix[-self.bytes:])
675
# This may not be compressed
676
self.prefix.encode(strio, None)
679
def decode(self, strio, length = None):
680
self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
681
self.bytes = int((128 - self.prefixLen) / 8.0)
683
self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
685
self.prefix.decode(strio)
688
def __eq__(self, other):
689
if isinstance(other, Record_A6):
690
return (self.prefixLen == other.prefixLen and
691
self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
692
self.prefix == other.prefix and
693
self.ttl == other.ttl)
698
return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
702
return '<A6 %s %s (%d) ttl=%s>' % (
704
socket.inet_ntop(AF_INET6, self.suffix),
705
self.prefixLen, self.ttl
709
class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin): # EXPERIMENTAL
710
implements(IEncodable, IRecord)
713
compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
714
showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
716
def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
717
self.priority = int(priority)
718
self.weight = int(weight)
719
self.port = int(port)
720
self.target = Name(target)
721
self.ttl = str2time(ttl)
724
def encode(self, strio, compDict = None):
725
strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
726
# This can't be compressed
727
self.target.encode(strio, None)
730
def decode(self, strio, length = None):
731
r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
732
self.priority, self.weight, self.port = r
734
self.target.decode(strio)
738
return hash((self.priority, self.weight, self.port, self.target))
742
class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
743
implements(IEncodable, IRecord)
746
compareAttributes = ('subtype', 'hostname', 'ttl')
747
showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
749
def __init__(self, subtype=0, hostname='', ttl=None):
750
self.subtype = int(subtype)
751
self.hostname = Name(hostname)
752
self.ttl = str2time(ttl)
755
def encode(self, strio, compDict = None):
756
strio.write(struct.pack('!H', self.subtype))
757
self.hostname.encode(strio, compDict)
760
def decode(self, strio, length = None):
761
r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
763
self.hostname.decode(strio)
767
return hash((self.subtype, self.hostname))
771
class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
772
implements(IEncodable, IRecord)
775
compareAttributes = ('mbox', 'txt', 'ttl')
776
showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
778
def __init__(self, mbox='', txt='', ttl=None):
779
self.mbox = Name(mbox)
781
self.ttl = str2time(ttl)
784
def encode(self, strio, compDict = None):
785
self.mbox.encode(strio, compDict)
786
self.txt.encode(strio, compDict)
789
def decode(self, strio, length = None):
792
self.mbox.decode(strio)
793
self.txt.decode(strio)
797
return hash((self.mbox, self.txt))
801
class Record_HINFO(tputil.FancyStrMixin):
802
implements(IEncodable, IRecord)
805
showAttributes = ('cpu', 'os', 'ttl')
807
def __init__(self, cpu='', os='', ttl=None):
808
self.cpu, self.os = cpu, os
809
self.ttl = str2time(ttl)
812
def encode(self, strio, compDict = None):
813
strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
814
strio.write(struct.pack('!B', len(self.os)) + self.os)
817
def decode(self, strio, length = None):
818
cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
819
self.cpu = readPrecisely(strio, cpu)
820
os = struct.unpack('!B', readPrecisely(strio, 1))[0]
821
self.os = readPrecisely(strio, os)
824
def __eq__(self, other):
825
if isinstance(other, Record_HINFO):
826
return (self.os.lower() == other.os.lower() and
827
self.cpu.lower() == other.cpu.lower() and
828
self.ttl == other.ttl)
833
return hash((self.os.lower(), self.cpu.lower()))
837
class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin): # EXPERIMENTAL
838
implements(IEncodable, IRecord)
844
compareAttributes = ('rmailbx', 'emailbx', 'ttl')
845
showAttributes = (('rmailbx', 'responsibility', '%s'),
846
('emailbx', 'errors', '%s'),
849
def __init__(self, rmailbx='', emailbx='', ttl=None):
850
self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
851
self.ttl = str2time(ttl)
854
def encode(self, strio, compDict = None):
855
self.rmailbx.encode(strio, compDict)
856
self.emailbx.encode(strio, compDict)
859
def decode(self, strio, length = None):
860
self.rmailbx, self.emailbx = Name(), Name()
861
self.rmailbx.decode(strio)
862
self.emailbx.decode(strio)
866
return hash((self.rmailbx, self.emailbx))
869
class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
870
implements(IEncodable, IRecord)
873
compareAttributes = ('preference', 'name', 'ttl')
874
showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
876
def __init__(self, preference=0, name='', ttl=None, **kwargs):
877
self.preference, self.name = int(preference), Name(kwargs.get('exchange', name))
878
self.ttl = str2time(ttl)
880
def encode(self, strio, compDict = None):
881
strio.write(struct.pack('!H', self.preference))
882
self.name.encode(strio, compDict)
885
def decode(self, strio, length = None):
886
self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
888
self.name.decode(strio)
891
warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklevel=2)
894
exchange = property(exchange)
897
return hash((self.preference, self.name))
901
# Oh god, Record_TXT how I hate thee.
902
class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
903
implements(IEncodable, IRecord)
907
showAttributes = compareAttributes = ('data', 'ttl')
909
def __init__(self, *data, **kw):
910
self.data = list(data)
911
# arg man python sucks so bad
912
self.ttl = str2time(kw.get('ttl', None))
915
def encode(self, strio, compDict = None):
917
strio.write(struct.pack('!B', len(d)) + d)
920
def decode(self, strio, length = None):
923
while soFar < length:
924
L = struct.unpack('!B', readPrecisely(strio, 1))[0]
925
self.data.append(readPrecisely(strio, L))
929
"Decoded %d bytes in TXT record, but rdlength is %d" % (
936
return hash(tuple(self.data))
942
headerSize = struct.calcsize( headerFmt )
944
# Question, answer, additional, and nameserver lists
945
queries = answers = add = ns = None
947
def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
948
auth=0, rCode=OK, trunc=0, maxSize=512):
949
self.maxSize = maxSize
964
def addQuery(self, name, type=ALL_RECORDS, cls=IN):
966
Add another query to this Message.
969
@param name: The name to query.
972
@param type: Query type
975
@param cls: Query class
977
self.queries.append(Query(name, type, cls))
980
def encode(self, strio):
982
body_tmp = StringIO.StringIO()
983
for q in self.queries:
984
q.encode(body_tmp, compDict)
985
for q in self.answers:
986
q.encode(body_tmp, compDict)
987
for q in self.authority:
988
q.encode(body_tmp, compDict)
989
for q in self.additional:
990
q.encode(body_tmp, compDict)
991
body = body_tmp.getvalue()
992
size = len(body) + self.headerSize
993
if self.maxSize and size > self.maxSize:
995
body = body[:self.maxSize - self.headerSize]
996
byte3 = (( ( self.answer & 1 ) << 7 )
997
| ((self.opCode & 0xf ) << 3 )
998
| ((self.auth & 1 ) << 2 )
999
| ((self.trunc & 1 ) << 1 )
1000
| ( self.recDes & 1 ) )
1001
byte4 = ( ( (self.recAv & 1 ) << 7 )
1002
| (self.rCode & 0xf ) )
1004
strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
1005
len(self.queries), len(self.answers),
1006
len(self.authority), len(self.additional)))
1010
def decode(self, strio, length = None):
1012
header = readPrecisely(strio, self.headerSize)
1013
r = struct.unpack(self.headerFmt, header)
1014
self.id, byte3, byte4, nqueries, nans, nns, nadd = r
1015
self.answer = ( byte3 >> 7 ) & 1
1016
self.opCode = ( byte3 >> 3 ) & 0xf
1017
self.auth = ( byte3 >> 2 ) & 1
1018
self.trunc = ( byte3 >> 1 ) & 1
1019
self.recDes = byte3 & 1
1020
self.recAv = ( byte4 >> 7 ) & 1
1021
self.rCode = byte4 & 0xf
1024
for i in range(nqueries):
1030
self.queries.append(q)
1032
items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
1033
for (l, n) in items:
1034
self.parseRecords(l, n, strio)
1037
def parseRecords(self, list, num, strio):
1038
for i in range(num):
1041
header.decode(strio)
1044
t = self.lookupRecordType(header.type)
1047
header.payload = t(ttl=header.ttl)
1049
header.payload.decode(strio, header.rdlength)
1055
def lookupRecordType(self, type):
1056
return globals().get('Record_' + QUERY_TYPES.get(type, ''), None)
1060
strio = StringIO.StringIO()
1062
return strio.getvalue()
1065
def fromStr(self, str):
1066
strio = StringIO.StringIO(str)
1070
class DNSDatagramProtocol(protocol.DatagramProtocol):
1078
def __init__(self, controller):
1079
self.controller = controller
1080
self.id = random.randrange(2 ** 10, 2 ** 15)
1084
self.id += randomSource() % (2 ** 10)
1086
if self.id not in self.liveMessages:
1090
def stopProtocol(self):
1091
self.liveMessages = {}
1093
self.transport = None
1095
def startProtocol(self):
1096
self.liveMessages = {}
1099
def writeMessage(self, message, address):
1100
self.transport.write(message.toStr(), address)
1102
def startListening(self):
1103
from twisted.internet import reactor
1104
reactor.listenUDP(0, self, maxPacketSize=512)
1106
def datagramReceived(self, data, addr):
1111
log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
1114
# Nothing should trigger this, but since we're potentially
1115
# invoking a lot of different decoding methods, we might as well
1116
# be extra cautious. Anything that triggers this is itself
1118
log.err(failure.Failure(), "Unexpected decoding error")
1121
if m.id in self.liveMessages:
1122
d, canceller = self.liveMessages[m.id]
1123
del self.liveMessages[m.id]
1125
# XXX we shouldn't need this hack of catching exceptioon on callback()
1131
if m.id not in self.resends:
1132
self.controller.messageReceived(m, self, addr)
1135
def removeResend(self, id):
1136
"""Mark message ID as no longer having duplication suppression."""
1138
del self.resends[id]
1142
def query(self, address, queries, timeout = 10, id = None):
1144
Send out a message with the given queries.
1146
@type address: C{tuple} of C{str} and C{int}
1147
@param address: The address to which to send the query
1149
@type queries: C{list} of C{Query} instances
1150
@param queries: The queries to transmit
1154
from twisted.internet import reactor
1156
if not self.transport:
1157
# XXX transport might not get created automatically, use callLater?
1158
self.startListening()
1163
self.resends[id] = 1
1164
m = Message(id, recDes=1)
1167
resultDeferred = defer.Deferred()
1168
cancelCall = reactor.callLater(timeout, self._clearFailed, resultDeferred, id)
1169
self.liveMessages[id] = (resultDeferred, cancelCall)
1171
self.writeMessage(m, address)
1172
return resultDeferred
1175
def _clearFailed(self, deferred, id):
1177
del self.liveMessages[id]
1180
deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
1183
class DNSProtocol(protocol.Protocol):
1192
def __init__(self, controller):
1193
self.controller = controller
1194
self.liveMessages = {}
1195
self.id = random.randrange(2 ** 10, 2 ** 15)
1200
self.id += randomSource() % (2 ** 10)
1202
if not self.liveMessages.has_key(self.id):
1207
def writeMessage(self, message):
1209
self.transport.write(struct.pack('!H', len(s)) + s)
1212
def connectionMade(self):
1213
self.controller.connectionMade(self)
1216
def dataReceived(self, data):
1217
self.buffer = self.buffer + data
1220
if self.length is None and len(self.buffer) >= 2:
1221
self.length = struct.unpack('!H', self.buffer[:2])[0]
1222
self.buffer = self.buffer[2:]
1224
if len(self.buffer) >= self.length:
1225
myChunk = self.buffer[:self.length]
1230
d = self.liveMessages[m.id]
1232
self.controller.messageReceived(m, self)
1234
del self.liveMessages[m.id]
1240
self.buffer = self.buffer[self.length:]
1247
def query(self, queries, timeout = None):
1249
Send out a message with the given queries.
1251
@type queries: C{list} of C{Query} instances
1252
@param queries: The queries to transmit
1257
d = self.liveMessages[id] = defer.Deferred()
1258
if timeout is not None:
1259
d.setTimeout(timeout)
1260
m = Message(id, recDes=1)
1262
self.writeMessage(m)