root / trunk / twisted / names / dns.py

Revision 25057, 49.5 kB (checked in by thijs, 8 months ago)

Merge cvar-interfaces-1853-2: Replace @cvar in interfaces with z.i.Attribute.

Author: thijs
Reviewer: exarkun
Fixes: #1853

Line 
1 # -*- test-case-name: twisted.names.test.test_dns -*-
2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 DNS protocol implementation.
8
9 Future Plans:
10     - Get rid of some toplevels, maybe.
11     - Put in a better lookupRecordType implementation.
12
13 @author: Moshe Zadka
14 @author: Jp Calderone
15 """
16
17 # System imports
18 import warnings
19
20 import struct, random, types, socket
21
22 try:
23     import cStringIO as StringIO
24 except ImportError:
25     import StringIO
26
27 AF_INET6 = socket.AF_INET6
28
29 from zope.interface import implements, Interface, Attribute
30
31
32 # Twisted imports
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
38
39
40 def randomSource():
41     """
42     Wrapper around L{randbytes.secureRandom} to return 2 random chars.
43     """
44     return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
45
46
47 PORT = 53
48
49 (A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
50  RP, AFSDB) = range(1, 19)
51 AAAA = 28
52 SRV = 33
53 NAPTR = 35
54 A6 = 38
55 DNAME = 39
56
57 QUERY_TYPES = {
58     A: 'A',
59     NS: 'NS',
60     MD: 'MD',
61     MF: 'MF',
62     CNAME: 'CNAME',
63     SOA: 'SOA',
64     MB: 'MB',
65     MG: 'MG',
66     MR: 'MR',
67     NULL: 'NULL',
68     WKS: 'WKS',
69     PTR: 'PTR',
70     HINFO: 'HINFO',
71     MINFO: 'MINFO',
72     MX: 'MX',
73     TXT: 'TXT',
74     RP: 'RP',
75     AFSDB: 'AFSDB',
76
77     # 19 through 27?  Eh, I'll get to 'em.
78
79     AAAA: 'AAAA',
80     SRV: 'SRV',
81     NAPTR: 'NAPTR',
82     A6: 'A6',
83     DNAME: 'DNAME'
84 }
85
86 IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
87
88 # "Extended" queries (Hey, half of these are deprecated, good job)
89 EXT_QUERIES = {
90     IXFR: 'IXFR',
91     AXFR: 'AXFR',
92     MAILB: 'MAILB',
93     MAILA: 'MAILA',
94     ALL_RECORDS: 'ALL_RECORDS'
95 }
96
97 REV_TYPES = dict([
98     (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
99 ])
100
101 IN, CS, CH, HS = range(1, 5)
102 ANY = 255
103
104 QUERY_CLASSES = {
105     IN: 'IN',
106     CS: 'CS',
107     CH: 'CH',
108     HS: 'HS',
109     ANY: 'ANY'
110 }
111 REV_CLASSES = dict([
112     (v, k) for (k, v) in QUERY_CLASSES.items()
113 ])
114
115
116 # Opcodes
117 OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
118 OP_NOTIFY = 4 # RFC 1996
119 OP_UPDATE = 5 # RFC 2136
120
121
122 # Response Codes
123 OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
124
125 class IRecord(Interface):
126     """
127     An single entry in a zone of authority.
128     """
129
130     TYPE = Attribute("An indicator of what kind of record this is.")
131
132
133 # Backwards compatibility aliases - these should be deprecated or something I
134 # suppose. -exarkun
135 from twisted.names.error import DomainError, AuthoritativeDomainError
136 from twisted.names.error import DNSQueryTimeoutError
137
138
139 def str2time(s):
140     suffixes = (
141         ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
142         ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
143     )
144     if isinstance(s, types.StringType):
145         s = s.upper().strip()
146         for (suff, mult) in suffixes:
147             if s.endswith(suff):
148                 return int(float(s[:-1]) * mult)
149         try:
150             s = int(s)
151         except ValueError:
152             raise ValueError, "Invalid time interval specifier: " + s
153     return s
154
155
156 def readPrecisely(file, l):
157     buff = file.read(l)
158     if len(buff) < l:
159         raise EOFError
160     return buff
161
162
163 class IEncodable(Interface):
164     """
165     Interface for something which can be encoded to and decoded
166     from a file object.
167     """
168
169     def encode(strio, compDict = None):
170         """
171         Write a representation of this object to the given
172         file object.
173
174         @type strio: File-like object
175         @param strio: The stream to which to write bytes
176
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
180         compression.
181         """
182
183     def decode(strio, length = None):
184         """
185         Reconstruct an object from data read from the given
186         file object.
187
188         @type strio: File-like object
189         @param strio: The stream from which bytes may be read
190
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.
196         """
197
198
199
200 class Charstr(object):
201     implements(IEncodable)
202
203     def __init__(self, string=''):
204         if not isinstance(string, str):
205             raise ValueError("%r is not a string" % (string,))
206         self.string = string
207
208
209     def encode(self, strio, compDict=None):
210         """
211         Encode this Character string into the appropriate byte format.
212
213         @type strio: file
214         @param strio: The byte representation of this Charstr will be written
215             to this file.
216         """
217         string = self.string
218         ind = len(string)
219         strio.write(chr(ind))
220         strio.write(string)
221
222
223     def decode(self, strio, length=None):
224         """
225         Decode a byte string into this Name.
226
227         @type strio: file
228         @param strio: Bytes will be read from this file until the full string
229             is decoded.
230
231         @raise EOFError: Raised when there are not enough bytes available from
232             C{strio}.
233         """
234         self.string = ''
235         l = ord(readPrecisely(strio, 1))
236         self.string = readPrecisely(strio, l)
237
238
239     def __eq__(self, other):
240         if isinstance(other, Charstr):
241             return self.string == other.string
242         return False
243
244
245     def __hash__(self):
246         return hash(self.string)
247
248
249     def __str__(self):
250         return self.string
251
252
253
254 class Name:
255     implements(IEncodable)
256
257     def __init__(self, name=''):
258         assert isinstance(name, types.StringTypes), "%r is not a string" % (name,)
259         self.name = name
260
261     def encode(self, strio, compDict=None):
262         """
263         Encode this Name into the appropriate byte format.
264
265         @type strio: file
266         @param strio: The byte representation of this Name will be written to
267         this file.
268
269         @type compDict: dict
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).
273         """
274         name = self.name
275         while name:
276             if compDict is not None:
277                 if name in compDict:
278                     strio.write(
279                         struct.pack("!H", 0xc000 | compDict[name]))
280                     return
281                 else:
282                     compDict[name] = strio.tell() + Message.headerSize
283             ind = name.find('.')
284             if ind > 0:
285                 label, name = name[:ind], name[ind + 1:]
286             else:
287                 label, name = name, ''
288                 ind = len(label)
289             strio.write(chr(ind))
290             strio.write(label)
291         strio.write(chr(0))
292
293
294     def decode(self, strio, length=None):
295         """
296         Decode a byte string into this Name.
297
298         @type strio: file
299         @param strio: Bytes will be read from this file until the full Name
300         is decoded.
301
302         @raise EOFError: Raised when there are not enough bytes available
303         from C{strio}.
304         """
305         self.name = ''
306         off = 0
307         while 1:
308             l = ord(readPrecisely(strio, 1))
309             if l == 0:
310                 if off > 0:
311                     strio.seek(off)
312                 return
313             if (l >> 6) == 3:
314                 new_off = ((l&63) << 8
315                             | ord(readPrecisely(strio, 1)))
316                 if off == 0:
317                     off = strio.tell()
318                 strio.seek(new_off)
319                 continue
320             label = readPrecisely(strio, l)
321             if self.name == '':
322                 self.name = label
323             else:
324                 self.name = self.name + '.' + label
325
326     def __eq__(self, other):
327         if isinstance(other, Name):
328             return str(self) == str(other)
329         return 0
330
331
332     def __hash__(self):
333         return hash(str(self))
334
335
336     def __str__(self):
337         return self.name
338
339 class Query:
340     """
341     Represent a single DNS query.
342
343     @ivar name: The name about which this query is requesting information.
344     @ivar type: The query type.
345     @ivar cls: The query class.
346     """
347
348     implements(IEncodable)
349
350     name = None
351     type = None
352     cls = None
353
354     def __init__(self, name='', type=A, cls=IN):
355         """
356         @type name: C{str}
357         @param name: The name about which to request information.
358
359         @type type: C{int}
360         @param type: The query type.
361
362         @type cls: C{int}
363         @param cls: The query class.
364         """
365         self.name = Name(name)
366         self.type = type
367         self.cls = cls
368
369
370     def encode(self, strio, compDict=None):
371         self.name.encode(strio, compDict)
372         strio.write(struct.pack("!HH", self.type, self.cls))
373
374
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)
379
380
381     def __hash__(self):
382         return hash((str(self.name).lower(), self.type, self.cls))
383
384
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__)
390
391
392     def __str__(self):
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)
396
397
398     def __repr__(self):
399         return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
400
401
402 class RRHeader:
403     """
404     A resource record header.
405
406     @cvar fmt: C{str} specifying the byte format of an RR.
407
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.
414     """
415
416     implements(IEncodable)
417
418     fmt = "!HHIH"
419
420     name = None
421     type = None
422     cls = None
423     ttl = None
424     payload = None
425     rdlength = None
426
427     cachedResponse = None
428
429     def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False):
430         """
431         @type name: C{str}
432         @param name: The name about which this reply contains information.
433
434         @type type: C{int}
435         @param type: The query type.
436
437         @type cls: C{int}
438         @param cls: The query class.
439
440         @type ttl: C{int}
441         @param ttl: Time to live for this record.
442
443         @type payload: An object implementing C{IEncodable}
444         @param payload: A Query Type specific data object.
445         """
446         assert (payload is None) or (payload.TYPE == type)
447
448         self.name = Name(name)
449         self.type = type
450         self.cls = cls
451         self.ttl = ttl
452         self.payload = payload
453         self.auth = auth
454
455
456     def encode(self, strio, compDict=None):
457         self.name.encode(strio, compDict)
458         strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
459         if self.payload:
460             prefix = strio.tell()
461             self.payload.encode(strio, compDict)
462             aft = strio.tell()
463             strio.seek(prefix - 2, 0)
464             strio.write(struct.pack('!H', aft - prefix))
465             strio.seek(aft, 0)
466
467
468     def decode(self, strio, length = None):
469         self.name.decode(strio)
470         l = struct.calcsize(self.fmt)
471         buff = readPrecisely(strio, l)
472         r = struct.unpack(self.fmt, buff)
473         self.type, self.cls, self.ttl, self.rdlength = r
474
475
476     def isAuthoritative(self):
477         return self.auth
478
479
480     def __str__(self):
481         t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
482         c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
483         return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
484
485
486     __repr__ = __str__
487
488
489
490 class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
491     """
492     A Resource Record which consists of a single RFC 1035 domain-name.
493
494     @type name: L{Name}
495     @ivar name: The name associated with this record.
496
497     @type ttl: C{int}
498     @ivar ttl: The maximum number of seconds which this record should be
499         cached.
500     """
501     implements(IEncodable, IRecord)
502
503     showAttributes = (('name', 'name', '%s'), 'ttl')
504     compareAttributes = ('name', 'ttl')
505
506     TYPE = None
507     name = None
508
509     def __init__(self, name='', ttl=None):
510         self.name = Name(name)
511         self.ttl = str2time(ttl)
512
513
514     def encode(self, strio, compDict = None):
515         self.name.encode(strio, compDict)
516
517
518     def decode(self, strio, length = None):
519         self.name = Name()
520         self.name.decode(strio)
521
522
523     def __hash__(self):
524         return hash(self.name)
525
526
527 # Kinds of RRs - oh my!
528 class Record_NS(SimpleRecord):
529     """
530     An authoritative nameserver.
531     """
532     TYPE = NS
533     fancybasename = 'NS'
534
535
536
537 class Record_MD(SimpleRecord):
538     """
539     A mail destination.
540
541     This record type is obsolete.
542
543     @see: L{Record_MX}
544     """
545     TYPE = MD
546     fancybasename = 'MD'
547
548
549
550 class Record_MF(SimpleRecord):
551     """
552     A mail forwarder.
553
554     This record type is obsolete.
555
556     @see: L{Record_MX}
557     """
558     TYPE = MF
559     fancybasename = 'MF'
560
561
562
563 class Record_CNAME(SimpleRecord):
564     """
565     The canonical name for an alias.
566     """
567     TYPE = CNAME
568     fancybasename = 'CNAME'
569
570
571
572 class Record_MB(SimpleRecord):
573     """
574     A mailbox domain name.
575
576     This is an experimental record type.
577     """
578     TYPE = MB
579     fancybasename = 'MB'
580
581
582
583 class Record_MG(SimpleRecord):
584     """
585     A mail group member.
586
587     This is an experimental record type.
588     """
589     TYPE = MG
590     fancybasename = 'MG'
591
592
593
594 class Record_MR(SimpleRecord):
595     """
596     A mail rename domain name.
597
598     This is an experimental record type.
599     """
600     TYPE = MR
601     fancybasename = 'MR'
602
603
604
605 class Record_PTR(SimpleRecord):
606     """
607     A domain name pointer.
608     """
609     TYPE = PTR
610     fancybasename = 'PTR'
611
612
613
614 class Record_DNAME(SimpleRecord):
615     """
616     A non-terminal DNS name redirection.
617
618     This record type provides the capability to map an entire subtree of the
619     DNS name space to another domain.  It differs from the CNAME record which
620     maps a single node of the name space.
621
622     @see: U{http://www.faqs.org/rfcs/rfc2672.html}
623     @see: U{http://www.faqs.org/rfcs/rfc3363.html}
624     """
625     TYPE = DNAME
626     fancybasename = 'DNAME'
627
628
629
630 class Record_A(tputil.FancyEqMixin):
631     """
632     An IPv4 host address.
633
634     @type address: C{str}
635     @ivar address: The packed network-order representation of the IPv4 address
636         associated with this record.
637
638     @type ttl: C{int}
639     @ivar ttl: The maximum number of seconds which this record should be
640         cached.
641     """
642     implements(IEncodable, IRecord)
643
644     compareAttributes = ('address', 'ttl')
645
646     TYPE = A
647     address = None
648
649     def __init__(self, address='0.0.0.0', ttl=None):
650         address = socket.inet_aton(address)
651         self.address = address
652         self.ttl = str2time(ttl)
653
654
655     def encode(self, strio, compDict = None):
656         strio.write(self.address)
657
658
659     def decode(self, strio, length = None):
660         self.address = readPrecisely(strio, 4)
661
662
663     def __hash__(self):
664         return hash(self.address)
665
666
667     def __str__(self):
668         return '<A address=%s ttl=%s>' % (self.dottedQuad(), self.ttl)
669     __repr__ = __str__
670
671
672     def dottedQuad(self):
673         return socket.inet_ntoa(self.address)
674
675
676
677 class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
678     """
679     Marks the start of a zone of authority.
680
681     This record describes parameters which are shared by all records within a
682     particular zone.
683
684     @type mname: L{Name}
685     @ivar mname: The domain-name of the name server that was the original or
686         primary source of data for this zone.
687
688     @type rname: L{Name}
689     @ivar rname: A domain-name which specifies the mailbox of the person
690         responsible for this zone.
691
692     @type serial: C{int}
693     @ivar serial: The unsigned 32 bit version number of the original copy of
694         the zone.  Zone transfers preserve this value.  This value wraps and
695         should be compared using sequence space arithmetic.
696
697     @type refresh: C{int}
698     @ivar refresh: A 32 bit time interval before the zone should be refreshed.
699
700     @type minimum: C{int}
701     @ivar minimum: The unsigned 32 bit minimum TTL field that should be
702         exported with any RR from this zone.
703
704     @type expire: C{int}
705     @ivar expire: A 32 bit time value that specifies the upper limit on the
706         time interval that can elapse before the zone is no longer
707         authoritative.
708
709     @type retry: C{int}
710     @ivar retry: A 32 bit time interval that should elapse before a failed
711         refresh should be retried.
712
713     @type ttl: C{int}
714     @ivar ttl: The default TTL to use for records served from this zone.
715     """
716     implements(IEncodable, IRecord)
717
718     fancybasename = 'SOA'
719     compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'minimum', 'ttl')
720     showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
721
722     TYPE = SOA
723
724     def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=0, minimum=0, ttl=None):
725         self.mname, self.rname = Name(mname), Name(rname)
726         self.serial, self.refresh = str2time(serial), str2time(refresh)
727         self.minimum, self.expire = str2time(minimum), str2time(expire)
728         self.retry = str2time(retry)
729         self.ttl = str2time(ttl)
730
731
732     def encode(self, strio, compDict = None):
733         self.mname.encode(strio, compDict)
734         self.rname.encode(strio, compDict)
735         strio.write(
736             struct.pack(
737                 '!LlllL',
738                 self.serial, self.refresh, self.retry, self.expire,
739                 self.minimum
740             )
741         )
742
743
744     def decode(self, strio, length = None):
745         self.mname, self.rname = Name(), Name()
746         self.mname.decode(strio)
747         self.rname.decode(strio)
748         r = struct.unpack('!LlllL', readPrecisely(strio, 20))
749         self.serial, self.refresh, self.retry, self.expire, self.minimum = r
750
751
752     def __hash__(self):
753         return hash((
754             self.serial, self.mname, self.rname,
755             self.refresh, self.expire, self.retry
756         ))
757
758
759
760 class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
761     """
762     A null record.
763
764     This is an experimental record type.
765
766     @type ttl: C{int}
767     @ivar ttl: The maximum number of seconds which this record should be
768         cached.
769     """
770     implements(IEncodable, IRecord)
771
772     fancybasename = 'NULL'
773     showAttributes = compareAttributes = ('payload', 'ttl')
774
775     TYPE = NULL
776
777     def __init__(self, payload=None, ttl=None):
778         self.payload = payload
779         self.ttl = str2time(ttl)
780
781
782     def encode(self, strio, compDict = None):
783         strio.write(self.payload)
784
785
786     def decode(self, strio, length = None):
787         self.payload = readPrecisely(strio, length)
788
789
790     def __hash__(self):
791         return hash(self.payload)
792
793
794
795 class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
796     """
797     A well known service description.
798
799     This record type is obsolete.  See L{Record_SRV}.
800
801     @type address: C{str}
802     @ivar address: The packed network-order representation of the IPv4 address
803         associated with this record.
804
805     @type protocol: C{int}
806     @ivar protocol: The 8 bit IP protocol number for which this service map is
807         relevant.
808
809     @type map: C{str}
810     @ivar map: A bitvector indicating the services available at the specified
811         address.
812
813     @type ttl: C{int}
814     @ivar ttl: The maximum number of seconds which this record should be
815         cached.
816     """
817     implements(IEncodable, IRecord)
818
819     fancybasename = "WKS"
820     compareAttributes = ('address', 'protocol', 'map', 'ttl')
821     showAttributes = [('_address', 'address', '%s'), 'protocol', 'ttl']
822
823     TYPE = WKS
824
825     _address = property(lambda self: socket.inet_ntoa(self.address))
826
827     def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
828         self.address = socket.inet_aton(address)
829         self.protocol, self.map = protocol, map
830         self.ttl = str2time(ttl)
831
832
833     def encode(self, strio, compDict = None):
834         strio.write(self.address)
835         strio.write(struct.pack('!B', self.protocol))
836         strio.write(self.map)
837
838
839     def decode(self, strio, length = None):
840         self.address = readPrecisely(strio, 4)
841         self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
842         self.map = readPrecisely(strio, length - 5)
843
844
845     def __hash__(self):
846         return hash((self.address, self.protocol, self.map))
847
848
849
850 class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
851     """
852     An IPv6 host address.
853
854     @type address: C{str}
855     @ivar address: The packed network-order representation of the IPv6 address
856         associated with this record.
857
858     @type ttl: C{int}
859     @ivar ttl: The maximum number of seconds which this record should be
860         cached.
861
862     @see: U{http://www.faqs.org/rfcs/rfc1886.html}
863     """
864     implements(IEncodable, IRecord)
865     TYPE = AAAA
866
867     fancybasename = 'AAAA'
868     showAttributes = (('_address', 'address', '%s'), 'ttl')
869     compareAttributes = ('address', 'ttl')
870
871     _address = property(lambda self: socket.inet_ntop(AF_INET6, self.address))
872
873     def __init__(self, address = '::', ttl=None):
874         self.address = socket.inet_pton(AF_INET6, address)
875         self.ttl = str2time(ttl)
876
877
878     def encode(self, strio, compDict = None):
879         strio.write(self.address)
880
881
882     def decode(self, strio, length = None):
883         self.address = readPrecisely(strio, 16)
884
885
886     def __hash__(self):
887         return hash(self.address)
888
889
890
891 class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
892     """
893     An IPv6 address.
894
895     This is an experimental record type.
896
897     @type prefixLen: C{int}
898     @ivar prefixLen: The length of the suffix.
899
900     @type suffix: C{str}
901     @ivar suffix: An IPv6 address suffix in network order.
902
903     @type prefix: L{Name}
904     @ivar prefix: If specified, a name which will be used as a prefix for other
905         A6 records.
906
907     @type bytes: C{int}
908     @ivar bytes: The length of the prefix.
909
910     @type ttl: C{int}
911     @ivar ttl: The maximum number of seconds which this record should be
912         cached.
913
914     @see: U{http://www.faqs.org/rfcs/rfc2874.html}
915     @see: U{http://www.faqs.org/rfcs/rfc3363.html}
916     @see: U{http://www.faqs.org/rfcs/rfc3364.html}
917     """
918     implements(IEncodable, IRecord)
919     TYPE = A6
920
921     fancybasename = 'A6'
922     showAttributes = (('_suffix', 'suffix', '%s'), ('prefix', 'prefix', '%s'), 'ttl')
923     compareAttributes = ('prefixLen', 'prefix', 'suffix', 'ttl')
924
925     _suffix = property(lambda self: socket.inet_ntop(AF_INET6, self.suffix))
926
927     def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
928         self.prefixLen = prefixLen
929         self.suffix = socket.inet_pton(AF_INET6, suffix)
930         self.prefix = Name(prefix)
931         self.bytes = int((128 - self.prefixLen) / 8.0)
932         self.ttl = str2time(ttl)
933
934
935     def encode(self, strio, compDict = None):
936         strio.write(struct.pack('!B', self.prefixLen))
937         if self.bytes:
938             strio.write(self.suffix[-self.bytes:])
939         if self.prefixLen:
940             # This may not be compressed
941             self.prefix.encode(strio, None)
942
943
944     def decode(self, strio, length = None):
945         self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
946         self.bytes = int((128 - self.prefixLen) / 8.0)
947         if self.bytes:
948             self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
949         if self.prefixLen:
950             self.prefix.decode(strio)
951
952
953     def __eq__(self, other):
954         if isinstance(other, Record_A6):
955             return (self.prefixLen == other.prefixLen and
956                     self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
957                     self.prefix == other.prefix and
958                     self.ttl == other.ttl)
959         return NotImplemented
960
961
962     def __hash__(self):
963         return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
964
965
966     def __str__(self):
967         return '<A6 %s %s (%d) ttl=%s>' % (
968             self.prefix,
969             socket.inet_ntop(AF_INET6, self.suffix),
970             self.prefixLen, self.ttl
971         )
972
973
974
975 class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
976     """
977     The location of the server(s) for a specific protocol and domain.
978
979     This is an experimental record type.
980
981     @type priority: C{int}
982     @ivar priority: The priority of this target host.  A client MUST attempt to
983         contact the target host with the lowest-numbered priority it can reach;
984         target hosts with the same priority SHOULD be tried in an order defined
985         by the weight field.
986
987     @type weight: C{int}
988     @ivar weight: Specifies a relative weight for entries with the same
989         priority. Larger weights SHOULD be given a proportionately higher
990         probability of being selected.
991
992     @type port: C{int}
993     @ivar port: The port on this target host of this service.
994
995     @type target: L{Name}
996     @ivar target: The domain name of the target host.  There MUST be one or
997         more address records for this name, the name MUST NOT be an alias (in
998         the sense of RFC 1034 or RFC 2181).  Implementors are urged, but not
999         required, to return the address record(s) in the Additional Data
1000         section.  Unless and until permitted by future standards action, name
1001         compression is not to be used for this field.
1002
1003     @type ttl: C{int}
1004     @ivar ttl: The maximum number of seconds which this record should be
1005         cached.
1006
1007     @see: U{http://www.faqs.org/rfcs/rfc2782.html}
1008     """
1009     implements(IEncodable, IRecord)
1010     TYPE = SRV
1011
1012     fancybasename = 'SRV'
1013     compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
1014     showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
1015
1016     def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
1017         self.priority = int(priority)
1018         self.weight = int(weight)
1019         self.port = int(port)
1020         self.target = Name(target)
1021         self.ttl = str2time(ttl)
1022
1023
1024     def encode(self, strio, compDict = None):
1025         strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
1026         # This can't be compressed
1027         self.target.encode(strio, None)
1028
1029
1030     def decode(self, strio, length = None):
1031         r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
1032         self.priority, self.weight, self.port = r
1033         self.target = Name()
1034         self.target.decode(strio)
1035
1036
1037     def __hash__(self):
1038         return hash((self.priority, self.weight, self.port, self.target))
1039
1040
1041
1042 class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
1043     """
1044     The location of the server(s) for a specific protocol and domain.
1045
1046     @type order: C{int}
1047     @ivar order: An integer specifying the order in which the NAPTR records
1048         MUST be processed to ensure the correct ordering of rules.  Low numbers
1049         are processed before high numbers.
1050
1051     @type preference: C{int}
1052     @ivar preference: An integer that specifies the order in which NAPTR
1053         records with equal "order" values SHOULD be processed, low numbers
1054         being processed before high numbers.
1055
1056     @type flag: L{Charstr}
1057     @ivar flag: A <character-string> containing flags to control aspects of the
1058         rewriting and interpretation of the fields in the record.  Flags
1059         aresingle characters from the set [A-Z0-9].  The case of the alphabetic
1060         characters is not significant.
1061
1062         At this time only four flags, "S", "A", "U", and "P", are defined.
1063
1064     @type service: L{Charstr}
1065     @ivar service: Specifies the service(s) available down this rewrite path.
1066         It may also specify the particular protocol that is used to talk with a
1067         service.  A protocol MUST be specified if the flags field states that
1068         the NAPTR is terminal.
1069
1070     @type regexp: L{Charstr}
1071     @ivar regexp: A STRING containing a substitution expression that is applied
1072         to the original string held by the client in order to construct the
1073         next domain name to lookup.
1074
1075     @type replacement: L{Name}
1076     @ivar replacement: The next NAME to query for NAPTR, SRV, or address
1077         records depending on the value of the flags field.  This MUST be a
1078         fully qualified domain-name.
1079
1080     @type ttl: C{int}
1081     @ivar ttl: The maximum number of seconds which this record should be
1082         cached.
1083
1084     @see: U{http://www.faqs.org/rfcs/rfc2915.html}
1085     """
1086     implements(IEncodable, IRecord)
1087     TYPE = NAPTR
1088
1089     compareAttributes = ('order', 'preference', 'flags', 'service', 'regexp',
1090                          'replacement')
1091     fancybasename = 'NAPTR'
1092     showAttributes = ('order', 'preference', ('flags', 'flags', '%s'),
1093                       ('service', 'service', '%s'), ('regexp', 'regexp', '%s'),
1094                       ('replacement', 'replacement', '%s'), 'ttl')
1095
1096     def __init__(self, order=0, preference=0, flags='', service='', regexp='',
1097                  replacement='', ttl=None):
1098         self.order = int(order)
1099         self.preference = int(preference)
1100         self.flags = Charstr(flags)
1101         self.service = Charstr(service)
1102         self.regexp = Charstr(regexp)
1103         self.replacement = Name(replacement)
1104         self.ttl = str2time(ttl)
1105
1106
1107     def encode(self, strio, compDict=None):
1108         strio.write(struct.pack('!HH', self.order, self.preference))
1109         # This can't be compressed
1110         self.flags.encode(strio, None)
1111         self.service.encode(strio, None)
1112         self.regexp.encode(strio, None)
1113         self.replacement.encode(strio, None)
1114
1115
1116     def decode(self, strio, length=None):
1117         r = struct.unpack('!HH', readPrecisely(strio, struct.calcsize('!HH')))
1118         self.order, self.preference = r
1119         self.flags = Charstr()
1120         self.service = Charstr()
1121         self.regexp = Charstr()
1122         self.replacement = Name()
1123         self.flags.decode(strio)
1124         self.service.decode(strio)
1125         self.regexp.decode(strio)
1126         self.replacement.decode(strio)
1127
1128
1129     def __hash__(self):
1130         return hash((
1131             self.order, self.preference, self.flags,
1132             self.service, self.regexp, self.replacement))
1133
1134
1135
1136 class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
1137     """
1138     Map from a domain name to the name of an AFS cell database server.
1139
1140     @type subtype: C{int}
1141     @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
1142         Volume Location Server for the named AFS cell.  In the case of subtype
1143         2, the host has an authenticated name server holding the cell-root
1144         directory node for the named DCE/NCA cell.
1145
1146     @type hostname: L{Name}
1147     @ivar hostname: The domain name of a host that has a server for the cell
1148         named by this record.
1149
1150     @type ttl: C{int}
1151     @ivar ttl: The maximum number of seconds which this record should be
1152         cached.
1153
1154     @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1155     """
1156     implements(IEncodable, IRecord)
1157     TYPE = AFSDB
1158
1159     fancybasename = 'AFSDB'
1160     compareAttributes = ('subtype', 'hostname', 'ttl')
1161     showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
1162
1163     def __init__(self, subtype=0, hostname='', ttl=None):
1164         self.subtype = int(subtype)
1165         self.hostname = Name(hostname)
1166         self.ttl = str2time(ttl)
1167
1168
1169     def encode(self, strio, compDict = None):
1170         strio.write(struct.pack('!H', self.subtype))
1171         self.hostname.encode(strio, compDict)
1172
1173
1174     def decode(self, strio, length = None):
1175         r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
1176         self.subtype, = r
1177         self.hostname.decode(strio)
1178
1179
1180     def __hash__(self):
1181         return hash((self.subtype, self.hostname))
1182
1183
1184
1185 class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
1186     """
1187     The responsible person for a domain.
1188
1189     @type mbox: L{Name}
1190     @ivar mbox: A domain name that specifies the mailbox for the responsible
1191         person.
1192
1193     @type txt: L{Name}
1194     @ivar txt: A domain name for which TXT RR's exist (indirection through
1195         which allows information sharing about the contents of this RP record).
1196
1197     @type ttl: C{int}
1198     @ivar ttl: The maximum number of seconds which this record should be
1199         cached.
1200
1201     @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1202     """
1203     implements(IEncodable, IRecord)
1204     TYPE = RP
1205
1206     fancybasename = 'RP'
1207     compareAttributes = ('mbox', 'txt', 'ttl')
1208     showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
1209
1210     def __init__(self, mbox='', txt='', ttl=None):
1211         self.mbox = Name(mbox)
1212         self.txt = Name(txt)
1213         self.ttl = str2time(ttl)
1214
1215
1216     def encode(self, strio, compDict = None):
1217         self.mbox.encode(strio, compDict)
1218         self.txt.encode(strio, compDict)
1219
1220
1221     def decode(self, strio, length = None):
1222         self.mbox = Name()
1223         self.txt = Name()
1224         self.mbox.decode(strio)
1225         self.txt.decode(strio)
1226
1227
1228     def __hash__(self):
1229         return hash((self.mbox, self.txt))
1230
1231
1232
1233 class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
1234     """
1235     Host information.
1236
1237     @type cpu: C{str}
1238     @ivar cpu: Specifies the CPU type.
1239
1240     @type os: C{str}
1241     @ivar os: Specifies the OS.
1242
1243     @type ttl: C{int}
1244     @ivar ttl: The maximum number of seconds which this record should be
1245         cached.
1246     """
1247     implements(IEncodable, IRecord)
1248     TYPE = HINFO
1249
1250     fancybasename = 'HINFO'
1251     showAttributes = compareAttributes = ('cpu', 'os', 'ttl')
1252
1253     def __init__(self, cpu='', os='', ttl=None):
1254         self.cpu, self.os = cpu, os
1255         self.ttl = str2time(ttl)
1256
1257
1258     def encode(self, strio, compDict = None):
1259         strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
1260         strio.write(struct.pack('!B', len(self.os)) + self.os)
1261
1262
1263     def decode(self, strio, length = None):
1264         cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
1265         self.cpu = readPrecisely(strio, cpu)
1266         os = struct.unpack('!B', readPrecisely(strio, 1))[0]
1267         self.os = readPrecisely(strio, os)
1268
1269
1270     def __eq__(self, other):
1271         if isinstance(other, Record_HINFO):
1272             return (self.os.lower() == other.os.lower() and
1273                     self.cpu.lower() == other.cpu.lower() and
1274                     self.ttl == other.ttl)
1275         return NotImplemented
1276
1277
1278     def __hash__(self):
1279         return hash((self.os.lower(), self.cpu.lower()))
1280
1281
1282
1283 class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
1284     """
1285     Mailbox or mail list information.
1286
1287     This is an experimental record type.
1288
1289     @type rmailbx: L{Name}
1290     @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
1291         for the mailing list or mailbox.  If this domain name names the root,
1292         the owner of the MINFO RR is responsible for itself.
1293
1294     @type emailbx: L{Name}
1295     @ivar emailbx: A domain-name which specifies a mailbox which is to receive
1296         error messages related to the mailing list or mailbox specified by the
1297         owner of the MINFO record.  If this domain name names the root, errors
1298         should be returned to the sender of the message.
1299
1300     @type ttl: C{int}
1301     @ivar ttl: The maximum number of seconds which this record should be
1302         cached.
1303     """
1304     implements(IEncodable, IRecord)
1305     TYPE = MINFO
1306
1307     rmailbx = None
1308     emailbx = None
1309
1310     fancybasename = 'MINFO'
1311     compareAttributes = ('rmailbx', 'emailbx', 'ttl')
1312     showAttributes = (('rmailbx', 'responsibility', '%s'),
1313                       ('emailbx', 'errors', '%s'),
1314                       'ttl')
1315
1316     def __init__(self, rmailbx='', emailbx='', ttl=None):
1317         self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
1318         self.ttl = str2time(ttl)
1319
1320
1321     def encode(self, strio, compDict = None):
1322         self.rmailbx.encode(strio, compDict)
1323         self.emailbx.encode(strio, compDict)
1324
1325
1326     def decode(self, strio, length = None):
1327         self.rmailbx, self.emailbx = Name(), Name()
1328         self.rmailbx.decode(strio)
1329         self.emailbx.decode(strio)
1330
1331
1332     def __hash__(self):
1333         return hash((self.rmailbx, self.emailbx))
1334
1335
1336
1337 class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
1338     """
1339     Mail exchange.
1340
1341     @type preference: C{int}
1342     @ivar preference: Specifies the preference given to this RR among others at
1343         the same owner.  Lower values are preferred.
1344
1345     @type name: L{Name}
1346     @ivar name: A domain-name which specifies a host willing to act as a mail
1347         exchange.
1348
1349     @type ttl: C{int}
1350     @ivar ttl: The maximum number of seconds which this record should be
1351         cached.
1352     """
1353     implements(IEncodable, IRecord)
1354     TYPE = MX
1355
1356     fancybasename = 'MX'
1357     compareAttributes = ('preference', 'name', 'ttl')
1358     showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
1359
1360     def __init__(self, preference=0, name='', ttl=None, **kwargs):
1361         self.preference, self.name = int(preference), Name(kwargs.get('exchange', name))
1362         self.ttl = str2time(ttl)
1363
1364     def encode(self, strio, compDict = None):
1365         strio.write(struct.pack('!H', self.preference))
1366         self.name.encode(strio, compDict)
1367
1368
1369     def decode(self, strio, length = None):
1370         self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
1371         self.name = Name()
1372         self.name.decode(strio)
1373
1374     def exchange(self):
1375         warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklevel=2)
1376         return self.name
1377
1378     exchange = property(exchange)
1379
1380     def __hash__(self):
1381         return hash((self.preference, self.name))
1382
1383
1384
1385 # Oh god, Record_TXT how I hate thee.
1386 class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
1387     """
1388     Freeform text.
1389
1390     @type data: C{list} of C{str}
1391     @ivar data: Freeform text which makes up this record.
1392     """
1393     implements(IEncodable, IRecord)
1394
1395     TYPE = TXT
1396
1397     fancybasename = 'TXT'
1398     showAttributes = compareAttributes = ('data', 'ttl')
1399
1400     def __init__(self, *data, **kw):
1401         self.data = list(data)
1402         # arg man python sucks so bad
1403         self.ttl = str2time(kw.get('ttl', None))
1404
1405
1406     def encode(self, strio, compDict = None):
1407         for d in self.data:
1408             strio.write(struct.pack('!B', len(d)) + d)
1409
1410
1411     def decode(self, strio, length = None):
1412         soFar = 0
1413         self.data = []
1414         while soFar < length:
1415             L = struct.unpack('!B', readPrecisely(strio, 1))[0]
1416             self.data.append(readPrecisely(strio, L))
1417             soFar += L + 1
1418         if soFar != length:
1419             log.msg(
1420                 "Decoded %d bytes in TXT record, but rdlength is %d" % (
1421                     soFar, length
1422                 )
1423             )
1424
1425
1426     def __hash__(self):
1427         return hash(tuple(self.data))
1428
1429
1430
1431 class Message:
1432     headerFmt = "!H2B4H"
1433     headerSize = struct.calcsize(headerFmt)
1434
1435     # Question, answer, additional, and nameserver lists
1436     queries = answers = add = ns = None
1437
1438     def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
1439                        auth=0, rCode=OK, trunc=0, maxSize=512):
1440         self.maxSize = maxSize
1441         self.id = id
1442         self.answer = answer
1443         self.opCode = opCode
1444         self.auth = auth
1445         self.trunc = trunc
1446         self.recDes = recDes
1447         self.recAv = recAv
1448         self.rCode = rCode
1449         self.queries = []
1450         self.answers = []
1451         self.authority = []
1452         self.additional = []
1453
1454
1455     def addQuery(self, name, type=ALL_RECORDS, cls=IN):
1456         """
1457         Add another query to this Message.
1458
1459         @type name: C{str}
1460         @param name: The name to query.
1461
1462         @type type: C{int}
1463         @param type: Query type
1464
1465         @type cls: C{int}
1466         @param cls: Query class
1467         """
1468         self.queries.append(Query(name, type, cls))
1469
1470
1471     def encode(self, strio):
1472         compDict = {}
1473         body_tmp = StringIO.StringIO()
1474         for q in self.queries:
1475             q.encode(body_tmp, compDict)
1476         for q in self.answers:
1477             q.encode(body_tmp, compDict)
1478         for q in self.authority:
1479             q.encode(body_tmp, compDict)
1480         for q in self.additional:
1481             q.encode(body_tmp, compDict)
1482         body = body_tmp.getvalue()
1483         size = len(body) + self.headerSize
1484         if self.maxSize and size > self.maxSize:
1485             self.trunc = 1
1486             body = body[:self.maxSize - self.headerSize]
1487         byte3 = (( ( self.answer & 1 ) << 7 )
1488                  | ((self.opCode & 0xf ) << 3 )
1489                  | ((self.auth & 1 ) << 2 )
1490                  | ((self.trunc & 1 ) << 1 )
1491                  | ( self.recDes & 1 ) )
1492         byte4 = ( ( (self.recAv & 1 ) << 7 )
1493                   | (self.rCode & 0xf ) )
1494
1495         strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
1496                                 len(self.queries), len(self.answers),
1497                                 len(self.authority), len(self.additional)))
1498         strio.write(body)
1499
1500
1501     def decode(self, strio, length=None):
1502         self.maxSize = 0
1503         header = readPrecisely(strio, self.headerSize)
1504         r = struct.unpack(self.headerFmt, header)
1505         self.id, byte3, byte4, nqueries, nans, nns, nadd = r
1506         self.answer = ( byte3 >> 7 ) & 1
1507         self.opCode = ( byte3 >> 3 ) & 0xf
1508         self.auth = ( byte3 >> 2 ) & 1
1509         self.trunc = ( byte3 >> 1 ) & 1
1510         self.recDes = byte3 & 1
1511         self.recAv = ( byte4 >> 7 ) & 1
1512         self.rCode = byte4 & 0xf
1513
1514         self.queries = []
1515         for i in range(nqueries):
1516             q = Query()
1517             try:
1518                 q.decode(strio)
1519             except EOFError:
1520                 return
1521             self.queries.append(q)
1522
1523         items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
1524         for (l, n) in items:
1525             self.parseRecords(l, n, strio)
1526
1527
1528     def parseRecords(self, list, num, strio):
1529         for i in range(num):
1530             header = RRHeader()
1531             try:
1532                 header.decode(strio)
1533             except EOFError:
1534                 return
1535             t = self.lookupRecordType(header.type)
1536             if not t:
1537                 continue
1538             header.payload = t(ttl=header.ttl)
1539             try:
1540                 header.payload.decode(strio, header.rdlength)
1541             except EOFError:
1542                 return
1543             list.append(header)
1544
1545
1546     def lookupRecordType(self, type):
1547         return globals().get('Record_' + QUERY_TYPES.get(type, ''), None)
1548
1549
1550     def toStr(self):
1551         strio = StringIO.StringIO()
1552         self.encode(strio)
1553         return strio.getvalue()
1554
1555
1556     def fromStr(self, str):
1557         strio = StringIO.StringIO(str)
1558         self.decode(strio)
1559
1560 class DNSMixin(object):
1561     """
1562     DNS protocol mixin shared by UDP and TCP implementations.
1563     """
1564     id = None
1565     liveMessages = None
1566
1567     def __init__(self, controller):
1568         self.controller = controller
1569         self.id = random.randrange(2 ** 10, 2 ** 15)
1570
1571
1572     def pickID(self):
1573         """
1574         Return a unique ID for queries.
1575         """
1576         while True:
1577             id = randomSource()
1578             if id not in self.liveMessages:
1579                 return id
1580
1581
1582     def callLater(self, period, func, *args):
1583         """
1584         Wrapper around reactor.callLater, mainly for test purpose.
1585         """
1586         from twisted.internet import reactor
1587         return reactor.callLater(period, func, *args)
1588
1589     def _query(self, queries, timeout, id, writeMessage):
1590         """
1591         Send out a message with the given queries.
1592
1593         @type queries: C{list} of C{Query} instances
1594         @param queries: The queries to transmit
1595
1596         @type timeout: C{int} or C{float}
1597         @param timeout: How long to wait before giving up
1598
1599         @type id: C{int}
1600         @param id: Unique key for this request
1601
1602         @type writeMessage: C{callable}
1603         @param writeMessage: One-parameter callback which writes the message
1604
1605         @rtype: C{Deferred}
1606         @return: a C{Deferred} which will be fired with the result of the
1607             query, or errbacked with any errors that could happen (exceptions
1608             during writing of the query, timeout errors, ...).
1609         """
1610         m = Message(id, recDes=1)
1611         m.queries = queries
1612
1613         try:
1614             writeMessage(m)
1615         except:
1616             return defer.fail()
1617
1618         resultDeferred = defer.Deferred()
1619         cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
1620         self.liveMessages[id] = (resultDeferred, cancelCall)
1621
1622         return resultDeferred
1623
1624     def _clearFailed(self, deferred, id):
1625         """
1626         Clean the Deferred after a timeout.
1627         """
1628         try:
1629             del self.liveMessages[id]
1630         except KeyError:
1631             pass
1632         deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
1633
1634
1635 class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
1636     """
1637     DNS protocol over UDP.
1638     """
1639     resends = None
1640
1641     def stopProtocol(self):
1642         """
1643         Stop protocol: reset state variables.
1644         """
1645         self.liveMessages = {}
1646         self.resends = {}
1647         self.transport = None
1648
1649     def startProtocol(self):
1650         """
1651         Upon start, reset internal state.
1652         """
1653         self.liveMessages = {}
1654         self.resends = {}
1655
1656     def writeMessage(self, message, address):
1657         """
1658         Send a message holding DNS queries.
1659
1660         @type message: L{Message}
1661         """
1662         self.transport.write(message.toStr(), address)
1663
1664     def startListening(self):
1665         from twisted.internet import reactor
1666         reactor.listenUDP(0, self, maxPacketSize=512)
1667
1668     def datagramReceived(self, data, addr):
1669         """
1670         Read a datagram, extract the message in it and trigger the associated
1671         Deferred.
1672         """
1673         m = Message()
1674         try:
1675             m.fromStr(data)
1676         except EOFError:
1677             log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
1678             return
1679         except:
1680             # Nothing should trigger this, but since we're potentially
1681             # invoking a lot of different decoding methods, we might as well
1682             # be extra cautious.  Anything that triggers this is itself
1683             # buggy.
1684             log.err(failure.Failure(), "Unexpected decoding error")
1685             return
1686
1687         if m.id in self.liveMessages:
1688             d, canceller = self.liveMessages[m.id]
1689             del self.liveMessages[m.id]
1690             canceller.cancel()
1691             # XXX we shouldn't need this hack of catching exception on callback()
1692             try:
1693                 d.callback(m)
1694             except:
1695                 log.err()
1696         else:
1697             if m.id not in self.resends:
1698                 self.controller.messageReceived(m, self, addr)
1699
1700
1701     def removeResend(self, id):
1702         """
1703         Mark message ID as no longer having duplication suppression.
1704         """
1705         try:
1706             del self.resends[id]
1707         except KeyError:
1708             pass
1709
1710     def query(self, address, queries, timeout=10, id=None):
1711         """
1712         Send out a message with the given queries.
1713
1714         @type address: C{tuple} of C{str} and C{int}
1715         @param address: The address to which to send the query
1716
1717         @type queries: C{list} of C{Query} instances
1718         @param queries: The queries to transmit
1719
1720         @rtype: C{Deferred}
1721         """
1722         if not self.transport:
1723             # XXX transport might not get created automatically, use callLater?
1724             try:
1725                 self.startListening()
1726             except CannotListenError:
1727                 return defer.fail()
1728
1729         if id is None:
1730             id = self.pickID()
1731         else:
1732             self.resends[id] = 1
1733
1734         def writeMessage(m):
1735             self.writeMessage(m, address)
1736
1737         return self._query(queries, timeout, id, writeMessage)
1738
1739
1740 class DNSProtocol(DNSMixin, protocol.Protocol):
1741     """
1742     DNS protocol over TCP.
1743     """
1744     length = None
1745     buffer = ''
1746
1747     def writeMessage(self, message):
1748         """
1749         Send a message holding DNS queries.
1750
1751         @type message: L{Message}
1752         """
1753         s = message.toStr()
1754         self.transport.write(struct.pack('!H', len(s)) + s)
1755
1756     def connectionMade(self):
1757         """
1758         Connection is made: reset internal state, and notify the controller.
1759         """
1760         self.liveMessages = {}
1761         self.controller.connectionMade(self)
1762
1763
1764     def connectionLost(self, reason):
1765         """
1766         Notify the controller that this protocol is no longer
1767         connected.
1768         """
1769         self.controller.connectionLost(self)
1770
1771
1772     def dataReceived(self, data):
1773         self.buffer += data
1774
1775         while self.buffer:
1776             if self.length is None and len(self.buffer) >= 2:
1777                 self.length = struct.unpack('!H', self.buffer[:2])[0]
1778                 self.buffer = self.buffer[2:]
1779
1780             if len(self.buffer) >= self.length:
1781                 myChunk = self.buffer[:self.length]
1782                 m = Message()
1783                 m.fromStr(myChunk)
1784
1785                 try:
1786                     d, canceller = self.liveMessages[m.id]
1787                 except KeyError:
1788                     self.controller.messageReceived(m, self)
1789                 else:
1790                     del self.liveMessages[m.id]
1791                     canceller.cancel()
1792                     # XXX we shouldn't need this hack
1793                     try:
1794                         d.callback(m)
1795                     except:
1796                         log.err()
1797
1798                 self.buffer = self.buffer[self.length:]
1799                 self.length = None
1800             else:
1801                 break
1802
1803
1804     def query(self, queries, timeout=60):
1805         """
1806         Send out a message with the given queries.
1807
1808         @type queries: C{list} of C{Query} instances
1809         @param queries: The queries to transmit
1810
1811         @rtype: C{Deferred}
1812         """
1813         id = self.pickID()
1814         return self._query(queries, timeout, id, self.writeMessage)
1815
Note: See TracBrowser for help on using the browser.